Shell Scripting

Bin Zhang

August 6, 2020

1. Basic scripting building

1.1 Display messages

Use echo to display messages.

echo "Hello world!"

1.2 Use variables

Define and reference a variable.

val=10
echo ${val}

In most cases, the curly brackets can be ignored.

val=str
# ignore curly brackets
echo $val
# ignoring curly brackets will lead to error
echo ${val}ing

Define and reference an array.

a[0]=1
echo ${a[0]}
# or
a = (1 2 3)
echo ${a[0]}

Note: bash treats all values as string.

1.3 Exit

Use echo $? to check the exit status. Use exit N to return the status N.

2. Conditional statements

2.1 if-then statements

if command; then
    commands
elif command; then
    commands
else
    commands
fi

The commands under the then statement will be executed only if the command after if statement is a valid command and the exit status is zero.

2.2 Testing conditions

Besides commands, bash provides a way to test conditions. There are three types of conditions:

Use double parentheses for numeric comparisons, including most operators in C and ** for exponentiation.

if (( expression )); then
    commands
fi

Use double square brackets for string comparisons, including normal string operators and =~ for regular expression.

if [[ comparison ]]; then
    commands
fi

The file comparisons are the most powerful and most used comparisons in shell scripting.

if [ condition ]; then
    commands
fi
Comparison Description
-e file Check if file exists
-d file Check if file exists and is a directory
-f file Check if file exists and is a file
-r file Check if file exists and is readable
-w file Check if file exists and is writable
-x file Check if file exists and is executable
-s file Check if file exists and is not empty
-O file Check if file exists and is owned by the current user
-G file Check if file exists and is owned by the current group
file1 -nt file2 Check if file1 is newer than file2
file1 -ot file2 Check if file1 is older than file2

In addition, you can combine conditions using AND (&&) and OR (||).

2.3 case statements

case variable in
    pattern1)
        commands
        ;;
    pattern2 | pattern3)
        commands
        ;;
    *)
        commands
        ;;
esac

3. Looping statements

3.1 for statements

for var in words; do
    commands
done

We can create words by brace expansion, wildcards or command substitution.

# brace expansion
for var in {a..z}; do
    commands
done

# wildcards
for var in *.txt; do
    commands
done

# command substitution
for var in $(command); do
    commands
done

We can change the value of IFS to change separator temporarily.

IFS.OLD=$IFS
IFS=$'\n':;"
commands
IFS=$IFS.OLD

Bash also provides the C-style for statements.

for (( expression1; expression2; expression3 )); do
    commands
done

3.2 while and until statements

# while
while command; do
    commands
done

# until
until command; do
    commands
done

3.3 Control the loop

Two commands are used to control what happens inside of a loop:

break n can indicate the level of the loop to break out of. By default n is 1.

3.4 Process the output of a loop

You can redirect or pipe the output of a loop within the script.

# redirect
for var in words; do
    commands
done > output.txt

# pipe
for var in words; do
    commands
done | grep txt

4. Handle input

4.1 Command line parameters

Both $* and $@ include all the command line parameters. The former takes all the parameters as a single word, while the latter takes all the parameters as separate words.

4.2 Being shifty

shift moves each parameter variable one position to the left by default. For example, after shift command, $3 refers to the fourth parameter, $2 refers to the third parameter, and $1 refers to the second parameter. Note that $0 always refers to the script name.

You can use shift to work with options.

while [ -n "$1" ]; do
    case "$1" in
        -a)
            commands
            ;;
        *)
            echo "Error"
            ;;
    esac
    shift
done

Usually the Linux-style command looks like command options parameter, and some options even requre another parameters, such as

ls -a
rm -rf file
curl -o outputfile url

In such cases, getopts is a better choice rather than shift.

getopts optstring opt

The optstring lists all valid option characters. A colon after a character means the option requires a value. Each time it is invoked, an option is assigned in opt, and the index of next option is assigned in OPTIND. If the option requres a value, it will be stored in OPTARG. Thus OPTIND should be initialized as 1 at first. Here is an example.

OPTIND=1
while getopts a:bc opt; do
    case "$opt" in
        a)
            # store value in val
            val=$OPTARG
            commands
            ;;
        b)
            commands
            ;;
        c)
            commands
        *)
            echo "Error"
            ;;
    esac
done

shift $(($OPTIND-1))
for var in "$@"; do
    commands
done

4.3 Get user input

There are two forms of read command:

# form 1
echo -n question
read var

# form 2
read -p question var

Use -t option to deal with timing out.

echo -n question
if read -t seconds var; then
    commands
else
    echo "Time out!"
fi

Use -n option to limit the input size.

# accept only one character
read -n1 var

Use -s option to prevent the input from displaying on the monitor.

echo -n "Enter your password: "
read -s passwd

Read from file.

cat file.txt | while read line; do
    commands
done

5. File descriptors

5.1 Redirect to a file descriptor

command >&2

5.2 Redirect a file descriptor to files

exec 1>file1
exec 0<file2

5.3 Create your own file descriptors

exec 3>file1
exec 4<file2
exec 5<>file3

Save the STDIN/STDOUT file descriptor location to another file descriptor temporarily to read/write a file.

exec 3>&1
exec 1>file1
echo "hello world" # write to file1
exec 1>&3
echo "hello world" # write to STDOUT

exec 3<&0
exec 0<file2
read var # read from file2
exec 0<&3
read var # read from STDIN

Close file descriptors.

exec 3>&-

6. Trap signals

To trap signals in a script:

trap command signals

# the trap can be modified
trap command2 signals

# also can be removed
trap -- signals

To trap a script exit

trap commands EXIT

7. Create functions

7.1 Basic script functions

There are two forms to define a function.

# form 1
function name {
    commands
}

#form 2
name() {
    commands
}

7.2 Return a value

The return statement is different from other programming languages. It return the exit status of the function. The exit status must be in the range of 0 to 255.

function func {
    commands
    return 0
}

func
echo "The exit status is $?"

To return a value like other programming languages, you can capture the output of the function.

function func {
    commands
    echo output
}

output=$(func)

7.3 Use variables in functions

The bash shell treats functions just like scripts. You can pass parameters to a function just like a regular script.

function func {
    command
}

func parameters

Parameters can also be passed to functions via global variables.

var=1

function func {
    var=2
}

func
# var=2
echo "var=$var"

To avoid modifying the value defined outside functions, you can use local variables.

var=1

function func {
    local var=2
}

func
# var=1
echo "var=$var"

7.4 Array variables and functions

Passing an array to a function is very confusing.

function func {
    local newarray
    newarray=(;'echo "$@"')
    echo ${newarray[*]}
}

myarray1=(1 2 3)
myarray2=($(func $myarray1))

7.5 Function recursion

function func {
    commands
    var=$(func)
    commands
}

7.6 Create a library

To use functions defined in other script files:

source script_file.sh

7.7 Use functions on the command line

Define functions in the .bashrc file, then you can use them in the command line.