Skip to content

Bash Scripting Notes

Common Bash scripting techniques.

Variable names

Encapsulating variables in curly brackets to avoid ambiguity:

FirstThree=ABC
echo "The first six are ${FirstThree}DEF"

Encapsulating variable in double quotes preserves whitespace:

FirstThree='A  B  C'
echo $FirstThree    # prints 'A B C'
echo "$FirstThree"   # prints 'A  B  C'

Use $() or backticks to assign variables from the output of a command:

now=$(date +%Y-%m-%d)
echo $now

Assign an array using brackets around the list values. Use {} for disambiguation, and '@' to access the whole array or an index value for a single element:

contents=($(ls))
echo ${contents[@]}
echo ${#contents[@]}  # number of elements in array
echo ${contents[0]}   # first element
echo ${contents[-1]}  # last element

You can also assign directly to an array element:

contents[3]=banana

Arguments are passed to scripts as space-separated values. '$0' holds the filename, '$1' is the first argument. '$#' holds the number of arguments passed to the script, and $@ holds a space-separated list of all arguments.

Arithmetic and Strings

Bash supports arithmetic operators using an arithmetic expression $(()):

INCOME=10000
TAX=$(($INCOME / 100 * 25))
echo $TAX

Several string operators are available. It is worth noting that the 'index' operator returns the numerical position of the search term, rather than the index-value of the search:

STRING="Nice to see you, to see you, nice"
echo ${#STRING}                         # length of string

expr index "$STRING" "s"                # 9: position of first 's' IN $STRING
echo ${STRING:$((9-1)):3}               # see: 3 characters from index 8
echo ${STRING:9-1:3}                    # see: 3 characters from index 8
echo ${STRING:8}                        # string from index 8 to end
expr index "$STRING" "you"              # 7: position of first 'y','o' or 'u' IN $STRING
echo ${STRING[@]/you/me}                # replace first 'you' with 'me'
echo ${STRING[@]//you/me}               # replace all occurrences of 'you' with 'me'
echo ${STRING[@]// you/}                # delete all occurrences of ' you'
echo ${STRING[@]/#Nice/Good}            # replace characters at the start of the string
echo ${STRING[@]/%nice/OK}              # replace characters at the end of the string
echo ${STRING[@]/%nice/$(pwd)}          # replace characters at the end of the string

Conditionals

The basic 'if' statement takes this form:

if [ <condition> ]; then
    # statements to execute
elif [ <condition ]; then
    # statements to execute
else
    # statements to execute
if

Conditionals can be a single test or combine multiple tests using '&&' or '||'. When using multiple tests, surround the conditional in double brackets '[[]]'.

Numeric comparisons are:

Operator Meaning
-lt less than
-gt greater than
-le less than or equal to
-ge greater than or equal to
-eq equal to
-ne not equal to

String comparisons are:

Operator Meaning
= is the same as
== is the same as
!= is different
-z is empty

Use double quotes around strings in comparisons to avoid shell expansion.

Case statements take the form:

case "$variable" in
    "$value1" )
        command
    ;;
    "$value2" | "$value3")
        command
    ;;
    ...
    * )
        default command
    ;;
esac

Loops

  • 'for' loops look like this:

    for arg in [list]
    do
        command $arg
    done
    
  • 'while' loops look like this:

    while [ condition ]
    do
        command
    done
    
  • 'until' loops look like this:

    until [ condition ]
    do
        command
    done
    

Loop constructs support 'break' and 'continue' statements.

An example 'for' loop:

NUMBERS=(12 13 16 18 21 25 23 28 33 35)

for n in ${NUMBERS[@]}
do
    if [ $n = 33 ]; then 
        break
    elif [ $(($n % 2)) = 1 ]; then
        echo $n
    else
        echo "Not an odd number"
    fi
done

Functions

Functions are declared are declared with the key word 'function':

function f1 {
    # access parameters as $1, $2, etc
}

Call the function by name, supplying any required arguments:

function f1 {
    echo "${1}, ${2}"
}

STMT=$(f1 "Hello" "World!")

echo $STMT

Use command substitution to collect the output from the function as a return value. Use the 'local' keyword to scope your variables to the functions scope to avoid changing variables in the global scope:

function f1 {
    GREET="Hello"
    local SCOPE="Earth"
    echo "${GREET}, ${SCOPE}!"
}

GREET="Goodbye"
SCOPE="World"

f1 $GREET $SCOPE           # changes $GREET globally

echo "${GREET}, ${SCOPE}!" # prints "Hello, World!"

When using command substitution around your function calls, the variables are scoped to the statement, so variables in the function will not overwrite globals:

function f1 {
    GREET="Hello"
    local SCOPE="Earth"
    echo "${GREET}, ${SCOPE}!"
}

GREET="Goodbye"
SCOPE="World"

echo $(f1 $GREET $SCOPE)   # prints "Hello, Earth!" but does not change global $GREET

echo "${GREET}, ${SCOPE}!" # prints "Goodbye, World!"

Special Variables and Signals

A number of special variables are available to scripts and functions:

Variable Name Meaning
$0 filename of current script
$n nth argument to script or function
$# number of arguments passed
$@ all arguments passed
$* all arguments passed
$? exit status of last command
$$ process id of current shell
$! process number of last background job

You can use trap to intercept OS signals such as Ctrl+C (SIGINT) or Ctrl+D (SIGQUIT):

trap <code to execute> $SIG1 $SIG2 ...

Signals can be specified by name or number: use kill -l for a list of signals. The code to execute can be inline or a function name.

File Tests

Name Test
-e file exists
-d directory exists
-r file is readable