linuxnewbie.org.gif
Tuesday, 12-Dec-2000 10:40:42 EST
Newbized Help Files articles discussion board bookshelf sensei's log advertising info

-CONTINUED

ARITHMETIC WITH BASH
bash allows you to perform arithmetic expressions. As you have already seen, arithmetic is performed using the expr command. However, this, like the true command, is considered to be slow. The reason is that in order to run true and expr, the shell has to start them up. A better way is to use a built in shell feature which is quicker. So an alternative to true, as we have also seen, is the ":" command. An alternative to using expr, is to enclose the arithmetic operation inside $((...)). This is different from $(...). The number of brackets will tell you that. Let us try it:

#!/bin/bash
x=8     # initialize x to 8
y=4     # initialize y to 4
# now we assign the sum of x and y to z:
z=$(($x + $y))
echo "The sum of $x + $y is $z"


As always, whichever one you choose, is purely up to you. If you feel more comfortable using expr to $((...)), by all means, use it.

bash is able to perform, addition, subtraction, multiplication, division, and modulus. Each action has an operator that corresponds to it:

ACTION             OPERATOR
Addition              +
Subtraction           -
Multiplication        *
Division              /
Modulus               %


Everyone should be familiar with the first four operations. If you do not know what modulus is, it is the value of the remainder when two values are divided. Here is an example of arithmetic in bash:

#!/bin/bash
x=5   # initialize x to 5
y=3   # initialize y to 3

add=$(($x + $y))   # add the values of x and y and assign it to variable add
sub=$(($x - $y))   # subtract the values of x and y and assign it to variable sub
mul=$(($x * $y))   # multiply the values of x and y and assign it to variable mul
div=$(($x / $y))   # divide the values of x and y and assign it to variable div
mod=$(($x % $y))   # get the remainder of x / y and assign it to variable mod

# print out the answers:
echo "Sum: $add"
echo "Difference: $sub"
echo "Product: $mul"
echo "Quotient: $div"
echo "Remainder: $mod"

Again, the above code could have been done with expr. For instance, instead of add=$(($x + $y)), you could have used add=$(expr $x + $y), or, add=`expr $x + $y`.

READING USER INPUT
Now we come to the fun part. You can make your program so that it will interact with the user, and the user can interact with it. The command to get input from the user, is read. read is a built in bash command that needs to make use of variables, as you will see:

#!/bin/bash
# gets the name of the user and prints a greeting
echo -n "Enter your name: "
read user_name
echo "Hello $user_name!"


The variable here is user_name. Of course you could have called it anything you like. read will wait for the user to enter something and then press ENTER. If the user does not enter anything, read will continue to wait until the ENTER key is pressed. If ENTER is pressed without entering anything, read will execute the next line of code. Try it. Here is the same example, only this time we check to see if the user enters something:

#!/bin/bash
# gets the name of the user and prints a greeting
echo -n "Enter your name: "
read user_name

# the user did not enter anything:
if [ -z "$user_name" ]; then
    echo "You did not tell me your name!"
    exit
fi

echo "Hello $user_name!"

Here, if the user presses the ENTER key without typing anything, our program will complain and exit. Otherwise, it will print the greeting. Getting user input is useful for interactive programs that require the user to enter certain things. For instance, you could create a simple database and have the user enter things in it to be added to your database.

FUNCTIONS
Functions make scripting much easier code easier to maintain. Basically it breaks up the program into smaller pieces. A function performs an action defined by you, and it can return a value if you wish. Before I continue, here is an example of a shell program using a function:

#!/bin/bash

# function hello() just prints a message
hello()
{
    echo "You are in function hello()"
}

echo "Calling function hello()..."
# call the hello() function:
hello
echo "You are now out of function hello()"

Try running the above. The function hello() has only one purpose here, and that is, to print a message. Functions can of course be made to do more complicated tasks. In the above, we called the hello() function by name by using the line:

hello

When this line is executed, bash searches the script for the line hello(). It finds it right at the top, and executes its contents.

Functions are always called by their function name, as we have seen in the above. When writing a function, you can either start with function_name(), as we did in the above, or if you want to make it more explicit, you can use the function function_name(). Here is an alternative way to write function hello():

function hello()
{
    echo "You are in function hello()"
}

Functions always have an empty start and closing brackets: "()", followed by a starting brace and an ending brace: "{...}". These braces mark the start and end of the function. Any code enclosed within the braces will be executed and will belong only to the function. Functions should always be defined before they are called. Let us look at the above program again, only this time we call the function before it is defined:

#!/bin/bash
echo "Calling function hello()..."
# call the hello() function:
hello echo "You are now out of function hello()"

# function hello() just prints a message
hello()
{
    echo "You are in function hello()"
}

Here is what we get when we try to run it:

xconsole$ ./hello.sh
Calling function hello()...
./hello.sh: hello: command not found
You are now out of function hello()

As you can see, we get an error. Therefore, always have your functions at the start of your code, or at least, before you call the function. Here is another example of using functions:

#!/bin/bash
# admin.sh - administrative tool

# function new_user() creates a new user account
new_user()
{
    echo "Preparing to add a new user..."
    sleep 2
    adduser     # run the adduser program
}

echo "1. Add user"
echo "2. Exit"

echo "Enter your choice: "
read choice

case $choice in
    1) adduser     # call the adduser() function
       ;;
    *) exit
       ;;
esac

In order for this to work properly, you will need to be the root user, since adduser is a program only root can run. Hopefully this example (short as it is) shows the usefulness of functions.

TRAPPING
You can use the built in command trap to trap signals in your programs. It is a good way to gracefully exit a program. For instance, if you have a program running, hitting CTRL-C will send the program an interrupt signal, which will kill the program. trap will allow you to capture this signal, and will give you a chance to either continue with the program, or to tell the user that the program is quitting. trap uses the following syntax:

trap action signal

action is what you want to do when the signal is activated, and signal is the signal to look for. A list of signals can be found by using the command trap -l. When using signals in your shell programs, omit the first three letters of the signal, usually SIG. For instance, the interrupt signal is SIGINT. In your shell programs, just use INT. You can also use the signal number that comes beside the signal name. For instance, the numerical signal value of SIGINTis 2. Try out the following program:

#!/bin/bash
# using the trap command

# trap CTRL-C and execute the sorry() function:
trap sorry INT

# function sorry() prints a message
sorry()
{
    echo "I'm sorry Dave. I can't do that."
    sleep 3
}

# count down from 10 to 1:
for i in 10 9 8 7 6 5 4 3 2 1; do
    $i seconds until system failure."
    sleep 1
done
echo "System failure."

Now, while the program is running and counting down, hit CTRL-C. This will send an interrupt signal to the program. However, the signal will be caught by the trap command, which will in turn execute the sorry() function. You can have trap ignore the signal by having "''" in place of the action. You can reset the trap by using a dash: "-". For instance:

# execute the sorry() function if SIGINT is caught:
trap sorry INT

# reset the trap:
trap - INT

# do nothing when SIGINT is caught:
trap '' INT

When you reset a trap, it defaults to its original action, which is, to interrupt the program and kill it. When you set it to do nothing, it does just that. Nothing. The program will continue to run, ignoring the signal.

AND & OR
We have seen the use of control structures, and how useful they are. There are two extra things that can be added. The AND: "&&" and the OR "||" statements. The AND statement looks like this:

condition_1 && condition_2

The AND statement first checks the leftmost condition. If it is true, then it checks the second condition. If it is true, then the rest of the code is executed. If condition_1 returns false, then condition_2 will not be executed. In other words:

if condition_1 is true, AND if condition_2 is true, then...

Here is an example making use of the AND statement:

#!/bin/bash
x=5
y=10
if [ "$x" -eq 5 ] && [ "$y" -eq 10 ]; then
    echo "Both conditions are true."
else
    echo "The conditions are not true."
fi

Here, we find that x and y both hold the values we are checking for, and so the conditions are true. If you were to change the value of x=5 to x=12, and then re-run the program, you would find that the condition is now false.

The OR statement is used in a similar way. The only difference is that it checks if the leftmost statement is false. If it is, then it goes on to the next statement, and the next:

condition_1 || condition_2

In pseudo code, this would translate to the following:

if condition_1 is true, OR if condition_2 is true, then...

Therefore, any subsequent code will be executed, provided at least one of the tested conditions is true:

#!/bin/bash
x=3
y=2
if [ "$x" -eq 5 ] || [ "$y" -eq 2 ]; then
    echo "One of the conditions is true."
else
    echo "None of the conditions are true."
fi

Here, you will see that one of the conditions is true. However, change the value of y and re-run the program. You will see that none of the conditions are true.

If you think about it, the if structure can be used in place of AND and OR, however, it would require nesting the if statements. Nesting means having an ifstructure within another if structure. Nesting is also possible with other control structures of course. Here is an example of a nested if structure, which is an equivalent of our previous AND code:

#!/bin/bash
x=5
y=10
if [ "$x" -eq 5 ]; then
    if [ "$y" -eq 10 ]; then
        echo "Both conditions are true."
    else
        echo "The conditions are not true."
    fi
fi

This achieves the same purpose as using the AND statement. It is much harder to read, and takes much longer to write. Save yourself the trouble and use the AND and OR statements.

USING ARGUMENTS
You may have noticed that most programs in Linux are not interactive. You are required to type arguments, otherwise, you get a "usage" message. Take the more command for instance. If you do not type a filename after it, it will respond with a "usage" message. It is possible to have your shell program work on arguments. For this, you need to know the "$#" variable. This variable stands for the total number of arguments passed to the program. For instance, if you run a program as follows:

xconsole$ foo argument

$# would have a value of one, because there is only one argument passed to the program. If you have two arguments, then $# would have a value of two. In addition to this, each word on the command line, that is, the program's name (in this case foo), and the argument, can be referred to as variables within the shell program. foo would be $0. argument would be $1. You can have up to 9 variables, from $0 (which is the program name), followed by $1 to $9 for each argument. Let us see this in action:

#!/bin/bash
# prints out the first argument
# first check if there is an argument:
if [ "$#" -ne 1 ]; then
    echo "usage: $0 "
fi

echo "The argument is $1"

This program expects one, and only one, argument in order to run the program. If you type less than one argument, or more than one, the program will print the usage message. Otherwise, if there is an argument passed to the program, the shell program will print out the argument you passed. Recall that $0 is the program's name. This is why it is used in the "usage" message. The last line makes use of $1. Recall that $1 holds the value of the argument that is passed to the program.

REDIRECTION AND PIPING
Normally, when you run a command, its output is printed to the screen. For instance:

xconsole$ echo "Hello World"
Hello World

Redirection allows you to redirect the output somewhere else, most likely, a file. The ">" operator is used to redirect output. Think of it as an arrow telling the output where to go. Here is an example of redirecting the output to a file:

xconsole$ echo "Hello World"> foo.file
xconsole$
cat foo.file
Hello World

Here, the output of echo "Hello World" is redirected to a file called foo.file. When we read the contents of the file, we see the output there. There is one problem with the ">" operator. It will overwrite the contents of any file. What if you want to append to it? Then you must use the append operator: ">>". It is used in the exact same was as the redirection operator, except that it will not overwrite the contents of the file, but add to it.

Finally, there is piping. Piping allows you to take the output from a program, and then run the output through another program. Piping is done using the pipe operator: "|". Note that this is not the lowercase "L". The pipe key can be obtained by using a key combination of SHIFT-\. Here is an example of piping:

xconsole$ cat /etc/passwd | grep xconsole
xconsole:x:1002:100:X_console,,,:/home/xconsole:/bin/bash

Here we read the entire /etc/passwd and then pipe the output to grep, which in turn, searches for the string xconsole and then prints the entire line containing the string, to the screen. You could have mixed this with redirection to save the final output to a file:

xconsole$ cat /etc/passwd | grep xconsole > foo.file
xconsole$
cat foo.file
xconsole:x:1002:100:X_console,,,:/home/xconsole:/bin/bash

It worked. /etc/passwd is read, and then the entire output is piped into grep to search for the string xconsole. The final output is then redirected and saved into foo.file. You will find redirection and piping to be useful tools when you write your shell programs.

TEMPORARY FILES
Often, there will be times when you need to create a temporary file. This file may be to temporarily hold some data, or just to work with a program. Once the program's purpose is completed, the file is often deleted. When you create a file, you have to give it a name. The problem is, the file you create, must not already existing in the directory you are creating it in. Otherwise, you could overwrite important data. In order to create a unique named temporary file, you need to use the "$$" symbol, by either prefixing, or suffixing it to the end of the file name. Take for example, you want to create a temporary file with the name hello. Now there is a chance that the user who runs your program, may have a file called hello, so that would clash with your program's temporary file. By creating a file called hello.$$, or $$hello instead, you will create a unique file. Try it:

xconsole$ touch hello
xconsole$
ls
hello
xconsole$
touch hello.$$
xconsole$
ls
hello     hello.689

There it is, your temporary file.

RETURN VALUES
Most programs return a value depending upon how they exit. For instance, if you look at the manual page for grep, it tells us that grep will return a 0 if a match was found, and a 1 if no match was found. Why do we care about the return value of a program? For various reasons. Let us say that you want to check if a particular user exists on the system. One way to do this would be to grep the user's name in the /etc/passwd file. Let us say the user's name is foobar:

xconsole$ grep "foobar" /etc/passwd
xconsole$

No output. That means that grep could not find a match. But it would be so much more helpful if a message saying that it could not find a match was printed. This is when you will need to capture the return value of the program. A special variable holds the return value of a program. This variable is $?. Take a look at the following piece of code:

#!/bin/bash
# grep for user foobar and pipe all output to /dev/null:
grep "foobar" > /dev/null 2>&1
# capture the return value and act accordingly:
if [ "$?" -eq 0 ]; then
   echo "Match found."
    exit
else
    echo "No match found."
fi

Now when you run the program, it will capture the return value of grep. If it equals to 0, then a match was found and the appropriate message is printed. Otherwise, it will print that there was no match found. This is a very basic use of getting a return value from a program. As you continue practicing, you will find that there will be times when you need the return value of a program to do what you want.

Now what if you want your shell script to return a value upon exiting? The exit command takes one argument. A number to return. Normally the number 0 is used to denote a successful exit, no errors occurred. Anything higher or lower than 0 normally means an error has occurred. This is for you, the programmer to decide. Let us look at this program:

#!/bin/bash
if [ -f "/etc/passwd" ]; then
    echo "Password file exists."
    exit 0
else
    echo "No such file."
    exit 1
fi

By specifying return values upon exit, other shell scripts you write making use of this script will be able to capture its return value.

Functions can also return values. This can be done by using the return command, which takes one argument, a number. It is used just like the way exit is used, except that it applies to functions. A quick example:

check_passwd()
{
    # check if the passwd file exists:
    if [ -f "/etc/passwd" ]; then
       echo "Password file exists."
        # found it, return a 0:
       return 0
   else
        # could not find it, return a 1:
       echo "No such file."
       return 1
   fi
}

# get a return value from the check_passwd function:
foo=check_passwd
# check the value:
if [ "$foo" -eq 0 ]; then
    echo "File exists."
    exit 0
else
    echo "No such file."
    exit 1
fi

Look at the code carefully. It is not hard to understand. We start by having a variable called foo, that takes the return value from the function check_passwd(). In check_passwd(), we check to see if the /etc/passwd file exists. If it does, we return a 0, otherwise, we return a 1. Now, if it exists and a 0 is returned, then the variable foo will now have the value 0. If a 1 was returned, then the variable foo will then have a value of 1. The next thing that is done here is to check the value of variable foo, and to print an appropriate messsage, and exit with a return value of either 0 (for success) or 1 (for fail).

CONCLUSION
That completes the introduction to bash scripting. Your scripting studies are not complete however. There is more to cover. As I said, this is an introduction. However, this is enough to get you started on modifying shell programs and writing your own. If you really want to master shell scripting, I recommend buying Learning the bash shell, 2nd Edition by O'Reilly & Associates, Inc. bash scripting is great for your everyday administrative use. But if you are planning on a much bigger project, you will want to use a much more powerful language like C or Perl. Good luck.

X_console shellscope@yahoo.com


[-previous page-]

[-NHF Control Panel-]
The Linux Channel at internet.com
Linux Planet
Linux Today
Linux Central
Linuxnewbie.org
PHPBuilder
Just Linux
Linux Programming
Linux Start
BSD Today
Apache Today
Enterprise Linux Today
BSD Central
All Linux Devices
SITE DESCRIPTIONS
[-What's New-]
Order a Linuxnewbie T-Shirt
Easy Webcam NHF
Directory Navigation NHF
Installing Snort 1.6.3 on SuSE 6.x-7.x
Customizing vim
The SysVinit NHF
Installing ALSA for the VT82C686 integrated sound
USB Creative Video Blaster II for Linux
Configuring the Intellimouse Explorer in XFree86 V4+
The beginnings of a distro NHF
Getting Past Carnivore?
Getting and Installing PGP
Getting your ATI Rage 128 Working
How to create a multiple partition system
Using Fdisk
Introduction to Programming in C/C++ with Vim
Adding a Hard drive in Linux -- In five steps
Installing ALSA for the Yamaha DS-XG Sound Card
Getting your Diamond Rio Mp3 Player to work with Linux
Bash Programming Cheat Sheet
Installing NVIDIA Drivers for Mandrake
Setting up Portsentry
Hard Drive Speed Tweak for Linux
Sensei's Log
Chat room
Join: Linuxnewbie.org SETI Black Belts!
Send in your news
Click the image to add Linuxnewbie.org to your MyNetscape Page
[-LNO Newsletter-]

[-Archive-]
The beginnings of a distro NHF
Connecting to the Internet using KPPP
Getting your SBLive to work
Unreal Tournament NHF
LWE Day 2 Pictures
LWE Day 1 Pictures
The LNO FAQ!
WoW (Words of Wisdom)
Other sites news
What is Linux?
What is Linux? part deux (ups & downs)
Search newsgroups
The List
ALS Report
Feedback Form
jobs.linuxtoday.com.gif
Match: Format: Sort by:
Search:
[-Quick Links-]

Copyright 2000 internet.com Corp. All Rights Reserved. Legal Notices Privacy Policy

internet.com.gif