bash Shell Scripting

 

Overview

Here is a basic bash shell script:

#!/bin/bash
echo "Hello World" 

The first line tells Linux to use the bash interpreter to run this script. To determine where your bash interpreter is located, run:

which bash

After saving the above script to hello.sh, to make it executable, we would run something like:

chmod 700 ./hello.sh

 

if ... else ... elif ... fi

The following checks for the existence of a file:

#!/bin/bash

if [ -f /etc/foo ]
then
    cp /etc/foo /tmp
    echo "Done."
else
    
    echo "This file does not exist."
    exit
fi 

Here are some file checking options:

Option Check if file...
-d ...is a directory
-e ...exists
-f ...is a regular file
-g ...has SGID permissions
-r ...is readable
-s ...has size > 0
-u ...has SUID permissions
-w ...is writable
-x ...is Executable

 

while ... do ... done


The while structure is a looping structure. While condition is true, do.

#!/bin/bash

while true 
do
    echo "Press CTRL-C to quit."
done

Here is an alternative:

#!/bin/bash

while :
do
    echo "Press CTRL-C to quit."
done

The latter is faster because it is a built in in bash.

To check the condition of of a variable:

#!/bin/bash

x=0      
while [ "$x" -le 10 ]
do
    echo "Current value of x: $x"
    x=$(expr $x + 1)
    sleep 1
done 

$(...) is a way of telling the shell that you want to run the command expr $x + 1, and assign its result to x. Any command enclosed in $(...) will be run:

#!/bin/bash

me=$(whoami)
echo "I am $me."

 

Check equality between numbers:

x -eq y x is equals to y
x -ne y x is not equals to y
x -gt y x is greater than y
x -lt y x is less than y

 

Check equality between strings:

x = y x is the same as y
x != y x is not the same as y
-n x Evaluates to true if x is not null
-z x Evaluates to true if x is null.

 

until ... do ... done


The while structure loops while the condition is true. The until structure loops until the condition is true.

#!/bin/bash

x=0
until [ "$x" -ge 10 ]
do
    echo "Current value of x: $x"
    x=$(expr $x + 1)
sleep 1
done 

 

for ... in ... do ... done


The for structure will loop through a range of variables.

#!/bin/bash

echo -n "Checking system for errors"
for dots in 1 2 3 4 5 6 7 8 9 10 
do
    echo -n "."
done
echo "System clean." 

The -n option prevents a new line from automatically being added.

#!/bin/bash

for x in muon gluon meson 
do
    echo "The value of variable x is: $x"
    sleep 1
done 

The following adds a .html extension to all files in the current directory:

#!/bin/bash

for file in * 
do
    echo "Adding .html extension to $file..."
    mv $file $file.html
    sleep 1
done

The splat (*) is a wild card character that will, in this case, match every file in the current directory.

 

case ... in ... esac


The case structure is useful for times where there are a lot of conditions to be checked, and you do not want to have to use if over and over again.

#!/bin/bash

x=5      

case $x in
    0) echo "Value of x is 0."
       ;;
    5) echo "Value of x is 5."
       ;;
    9) echo "Value of x is 9."
       ;;
    *) echo "Unrecognized value."
esac 

The case structure will check the value of x against 3 possibilities. If all the checks fail, it will produce a message.

 

Quotes


There are three types of quotation marks:

Double " "
Single ' '
Back ` `

Double quotes are used mainly to hold strings of words and whitespace. A string enclosed in double quotes is treated as one argument. For example...

> mkdir hello world
> ls -F
hello/      world/ 
...versus...

> mkdir "hello world"
> ls -F
hello/      hello world/      world/

If a variable is enclosed in double quotes, its value will be evaluated. If it is enclosed in single quotes, its value will not be evaluated.

#!/bin/bash

x=5      
echo "Using double quotes, the value of x is: $x"
echo 'Using single quotes, the value of x is: $x'

Single quotes can be used to preserve whitespace just like double quotes:

> mkdir 'hello world'
> ls -F
hello world/ 

Back quotes, also called back tics, are completely different from double and single quotes, and are generally used to evaluate expressions. For example:

x=`expr $x + 1`

...and...

$!/bin/bash

echo "I am `whoami`"

 

Math With Bash


The fastest way to do math is to use bash's built-in shell feature

#!/bin/bash

x=8      
y=4      
z=$(($x + $y))

echo "The sum of $x + $y is $z"

bash has the following math operators:

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

For example:

#!/bin/bash

x=5   
y=3   
  
add=$(($x + $y)) 
sub=$(($x - $y)) 
mul=$(($x * $y)) 
div=$(($x / $y)) 
mod=$(($x % $y)) 
  
echo "Sum:        $add"
echo "Difference: $sub"
echo "Product:    $mul"
echo "Quotient:   $div"
echo "Remainder:  $mod"

The above can be written using expr:

add=$(expr $x + $y) 
...and...

add=`expr $x + $y`. 

 

User Input


To get input from a user:

#!/bin/bash

echo -n "Enter your name: "
read user_name
echo "Hello $user_name!"

The read function stores all input into a variable until the user the <Enter> key.

#!/bin/bash

echo -n "Enter your name: "
read user_name
  

if [ -z "$user_name" ]
then
    echo "You did not tell me your name!"
exit
fi

echo "Hello $user_name!"

 

Functions

Functions break up a program into smaller pieces.

#!/bin/bash

hello()
{
    echo "Inside function hello()"
} 

echo "Entering function hello()" 
hello
echo "Outside of function hello()" 

Functions should always be defined before they are called. The following is incorrect:

#!/bin/bash

echo "Entering function hello()..."
hello
echo "Outside of function hello()" 

hello()
{
    echo "Inside function hello()"
} 

Always have your functions at the start of your code, or at least, before you call the function.

Here is a more sophisticated example:

#!/bin/bash
  
new_user()
{
    echo "Preparing to add a new user..."
    sleep 2
    adduser      
} 

echo "1. Add user"
echo "2. Exit" 
  
echo "Enter your choice: "
read choice

case $choice in
    1) new_user      
       ;;
    *) exit
       ;;
esac 

 

Trapping

The built in trap command is used to gracefully exit programs. For instance, if you have a program running, hitting CTRL-C will send the program an interrupt signal, which will kill the program. 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 running 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 SIGINT is 2. Try out the following program:

#!/bin/bash

trap sorry INT 

sorry()
{
    echo "I'm sorry Dave. I can't do that."
    sleep 3
} 
  

for i in 10 9 8 7 6 5 4 3 2 1
do
    echo $i seconds until system failure."
    sleep 1
done
echo "System failure."

You can have trap ignore the signal by having "''" in place of the action. To reset a trap to it's original value, use a dash: "-" in place of the action.

trap sorry INT
trap - INT
trap '' INT 

When a trap is reset, 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

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.

#!/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.

The with an OR statement 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."
    echo "None of the conditions are true."
fi 

 

Arguments and Parameters


The $# variable stands for the total number of arguments passed to the program. For instance, if you run a program as follows:

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.

Each word on the command line to as variables within the shell program. foo would be $0. argument would be $1. You can have up to 9 variables.

#!/bin/bash

if [ "$#" -ne 1 ]
then
     echo "usage: $0 <argument>"
fi 

echo "The argument is $1"


$* represents all of the arguments on the command line.

 

Redirection AND Piping


Standard output (stdout) is printed to the screen. For instance:

echo "Hello World" Hello World

Redirection allows you to redirect the output somewhere else, such as a file or a tape drive.

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

Piping allows you to take the output from a program, and then run the output through another program.

cat /etc/passwd | grep username

 

Temporary Files


To create a uniquely named temp file, use $$ as either a prefix or suffix:

> touch hello
> ls
hello
> touch hello.$$
> ls
hello hello.689

 

Return Values

The exit command takes one argument. A number to return. 0 is used to denote a successful exit, no errors occurred. Anything higher or lower than 0 normally means an error has occurred.

#!/bin/bash

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

 

getopts

Syntax:

getopts optstring name [args]

getopts is used by shell procedures to parse positional parameters. optstring contains the option characters to be recognized; if a character is followed by a colon, the option is expected to have an argument, which should be separated from it by white space. The colon and question mark characters may not be used as option characters.

Here is an example:

while getopts ":d:j:u:p:r:t:a:" opt; do
  case $opt in
    d ) xdomain=$OPTARG ;;
    j ) xjobcode=$OPTARG ;;
    u ) xuser=$OPTARG ;;
    p ) xpasswd=$OPTARG ;;
    r ) xrealm=$OPTARG ;;
    t ) xtimeout=$OPTARG ;;
    a ) xargs="$xargs $OPTARG" ;;
    * ) echo $USAGE
         exit 1 ;;
  esac
done

Each time it is invoked, getopts places the next option in the shell variable name, initializing name if it does not exist, and the index of the next argument to be processed into the variable OPTIND. OPTIND is initialized to 1 each time the shell or a shell script is invoked. When an option requires an argument, getopts places that argument into the variable OPTARG. The shell does not reset OPTIND automatically; it must be manually reset between multiple calls to getopts within the same shell invocation if a new set of parameters is to be used.

When the end of options is encountered, getopts exits with a return value greater than zero. OPTIND is set to the index of the first non-option argument, and name is set to ?.

getopts normally parses the positional parameters, but if more arguments are given in args, getopts parses those instead.

getopts can report errors in two ways. If the first character of optstring is a colon, silent error reporting is used. In normal operation diagnostic messages are printed when invalid options or missing option arguments are encountered. If the variable OPTERR is set to 0, no error messages will be displayed, even if the first character of optstring is not a colon.

If an invalid option is seen, getopts places ? into name and, if not silent, prints an error message and unsets OPTARG. If getopts is silent, the option character found is placed in OPTARG and no diagnostic message is printed.

If a required argument is not found, and getopts is not silent, a question mark (?) is placed in name, OPTARG is unset, and a diagnostic message is printed. If getopts is silent, then a colon (:) is placed in name and OPTARG is set to the option character found.

getopts returns true if an option, specified or unspecified, is found. It returns false if the end of options is encountered or an error occurs.