Bash Scripting Basics & Conditions

Bash Basics

  • Use echo $PATH to display the available executable paths for your environment before you start writing your script.
    • NOTE: Your default $PATH can be modified and appended:
      • Use: PATH=$PATH:/something/something/bin
    • Add /home/user/bin and place scripts there to enable easy system-wide script execution.
  • To run a script not located in /bin reference the full script path, as in "/home/user/myscript" or use ./myscript to run it.
  • REMEMBER: Scripts must have proper executable permissions to be able to run.
    • Perform chmod u+x <scriptname> to enable script execution.

The Simplest Possible Bash Script

A script can be comprised of a single Linux command, as in this example I'm calling "myls":

#!/bin/bash
ls

Once you have written this and run myls the script simply runs ls on the current working directory.

We can replace ls with any other command we wish. Try out some of these commands in a bash script to see what they do:

  1. echo $USER or whoami
  2. pwd
  3. env
  4. echo $HOME
  5. echo $HISTFILE
  6. echo $HOSTNAME
  7. echo $PS1
  8. echo $SHELL

My dirlist Script

This made-up script, "dirlist" (see: my github repository), performs an ls on a given directory and logs the results to a log file in the user's $HOME directory. The syntax to run it is simply: dirlist </directory>.

dirlist terminal output:

For example, the output to the terminal when I ran "dirlist /home" on one of my servers was as such follows:

Operation Complete: Logged to /root/dir_list_log.

#### /home <ON> 22/03/2016 <AT> 01:53:31 AM ####
file1.txt
file2.txt
file3.txt
file4.txt
user
>>>> End of listing for /home. <<<<

Notice I added some formatting: the #### /home ... line with the time and date when the command was run, plus a 'end of listing' line. The 'operation complete' message shows where the results were logged.

dirlist log format:

When I viewed the contents of the log that was created when I ran the program (using cat $HOME/dir_list_log), I found that the following entry had been appended — as expected:

#### /home <ON> 22/03/2016 <AT> 01:53:31 AM ####
file1.txt
file2.txt
file3.txt
file4.txt
user
>>>> End of listing for /home. <<<<

This "dirlist" script does one thing and one thing only: print out and log the contents of a directory (see the script in its entirety below). It has limited utility, for sure... If we were being creative, perhaps we could imagine using it to keep track of a given directory's contents over time, setting it to be run automatically as a cronjob every x-number of minutes on the /etc directory or some other important system location. The fact that it appends to the log while running ls is key here (>> redirect).

The value of this script is mainly pedagogical; that is, it is a teaching and learning tool. It implements the date command, inserting current time/date into the log and console output. It shows off the >> redirect to append output to a file, which as stated above, can be quite useful for creating and maintaining logs of various system events and user actions.

Here is the "dirlist" bash script, followed by some final comments on the use of arguments and the if/then condition statements being used:

< view on github >

#!/bin/bash
# log/append contents of given directory to $HOME/dir_list_log with timestamp.

# create $location variable for re-use within the program

location=$1

# exit, print 'error' message if no $location argument given

if [ -z "$location" ]
  then
  echo "ERROR: Please provide a directory location to index."
  exit
fi

# create/append header message to dir_list_log file in $HOME
# include location given as argument
# include time and date of directory content retrieval

echo "#### $location <ON> \
`date +\"%d\"\/\"%m\"\/\"%Y\"` <AT> \
`date +\"%r\"` ####" \
>> $HOME/dir_list_log

# list the contents of directory given as input to command 'dirlist'
# output/append listing to dir_list_log

ls $location >> $HOME/dir_list_log

# create/append 'end of listing' message to dir_list_log

echo ">>>> End of listing for $location. <<<<" >> $HOME/dir_list_log
echo " " >> $HOME/dir_list_log

# output 'operation complete' message to console

echo "Operation Complete: Logged to $HOME/dir_list_log."
echo " "
echo "#### $location <ON> \
`date +\"%d\"\/\"%m\"\/\"%Y\"` <AT> \
`date +\"%r\"` ####"
ls -1 $location
echo ">>>> End of listing for $location. <<<<"
echo " "

Final Thoughts on the "dirlist" Script:

Two more important features to highlight:

  • location=$1 and ls $location -- The argument variable definition comes at the beginning of the script. The argument can then be fed to dirlist by the user by naming the directory to index.
  • if [ -z $location ] ... -- This if/then condition statement ensures that users enter a directory location as an argument. The script exits with an error message in this case. More on [ and the test commands later.

Variables & Arguments

  • (( ... )) -- used to interpret arithmetic operations
  • $# -- display the number of arguments passed to the script. one use-case is:
    • if (( $# == 0 ))
      then
      echo “No arguments have been passed to the script”
      fi
  • {<VARNAME>} -- Place curly braces around variable names to unambiguously identify them
    • VARIABLE=hello
      • echo $VARIABLE = hello
    • echo $VARIABLE1234 -- nothing happens since $VARIABLE1234 is not a variable
    • echo ${VARIABLE}1234 interprets the variable as is and adds on '1234' at the end:
      • echo ${VARIABLE}1234 = hello1234

Conditions in bash Scripting

  • [ -- test a given statement. output: (0) for True or (1) for False.
  • [[ -- another test command which allows for arithmetic operations and combining of multiple tests without using escape characters, such as "/":
    • The following test statement returns TRUE:
      • [[ ( 3 -gt 2 ) && ( 4 -lt 5 ) ]] && echo TRUE || echo FALSE
      • Read as: If 3>2 AND 4<5 then "TRUE"; else "FALSE".

3 filetest Example Programs:

  1. Using the test command:

    #!/bin/bash
    # "filetest" script returns 'true' message if it is a file
    FILE=$1
    # syntax: filetest <file-or-dir-name>
    if test -f $FILE
    then
    echo "$FILE is a file"
    fi

  2. Shorthand version: Uses the "[ test" plus && to execute the echo confirmation message only if [ test returns true:

    #!/bin/bash
    # "filetest" script returns 'true' message if it is a file
    FILE=$1
    # syntax: filetest <file-or-dir-name>
    [ -f $FILE ] && echo "$FILE is a file"

  3. Using if and [ test together:

    #!/bin/bash
    # "filetest" script returns 'true' message if it is a file
    FILE=$1
    # syntax: filetest <file-or-dir-name>
    if [ -f $FILE ]
    then
    echo "$FILE is a file"
    fi

The single bracket "[" if statements here are referred to as "test" brackets and are the oldest and most cross-compatible form of "test" command.

Basic syntax rules:

* You must quote every string
* No file globbing is allowed with single bracket test

Sample Script "mt" to Check for Empty String Variable:

#!/bin/bash
# "mt" = empty (string) checking tool
MTSTRING=""
if [ -z "$MTSTRING" ]
then
echo "Variable is empty."
else
echo "Variable is NOT empty; set to $MTSTRING."
fi

If you create this script and then run it using mt from the command line, you will see it return the 'variable is empty' message.

If you edit the MTSTRING variable in the script file itself, e.g. "MTSTRING=Hello World", and then run mt again, you will get the non-empty confirmation message: "Variable is NOT empty; set to Hello World."

NOTE: Withing single bracket "[" test statements, you must always put double-quotes around string variables. With double bracket tests "[[" this is not always required, but is considered best practice.

Flag Conditions:

  • -gt -- greater than
  • -lt -- less than
  • -ge -- greater than equal to
  • -le -- less than or equal to
  • -eq -- equal to
  • -ne -- not equalto
  • -f -- is file
  • -d -- is directory
  • -l -- is symlink

Sample 'Greater-than' Test Script:

if [ 5 -gt 4 ]
then
echo "TRUE: 5 is greater than 4."
fi

Double test-brackets: [[ ... ]]

  • [[ -- does allows shell globbing, unlike [
    • *.txt -- expands to [anything].txt
  • other features/limitations:
    • word-splitting is prevented (double-quotes not needed)
    • omitting double-quotes around string variables is not considered best practice

Two Sample check_string_contents Programs:

MYSTRING=sammie

if [[ "$MYSTRING" == *mmie* ]]
then
echo "TRUE"
fi

This first example determines if the given string "sammie" contains "mmie". This will return TRUE.

MYSTRING=sammie

if [[ $MYSTRING == *[sS]a* ]]
then
echo "TRUE"
fi

This second example determines if the string contains either "sa" or "Sa". Returns TRUE. Notice this second example contains no double-quotes.

Expanding files names using [ ... ] and [[ ... ]]

Sample program to return true if only one "*.txt" file exists in directory:

if [ -a *.txt ]
then
echo "Single test brackets with * expands to entire current directory."
echo "Produces error if more than 1 file exists."
echo "ERROR: There is at least one file that ends with \".txt\" in this directory."
fi

Sample program with double-brackets test returns true only if literal "*.txt" file exists:

if [[ -a *.txt ]]
then
echo "TRUE: There is a file named \"*.txt\" exactly."
fi

Double brackets also allow for:

  • && -- "and" operator
  • || -- "or" operator
  • =~ -- regular expression comparison operator:

    • =~ takes a string on the left and an extended regular expression on the right.
    • =~ returns a "0" for 'success' if the regular expression matches the string
    • =~ returns a "1" for failure
    • The example script below from user paxdiablo on Stack Overflow tests whether the value assigned to variable "$i" is a valid year, either 2007 or 2008:

      i="test" if [[ $i =~ 200[78] ]] ; then echo "OK" else echo "not OK" fi

    • With "test" it returns not OK; with "2007" it returns OK.

And/Or Logic Operators and Alternatives:

  • && -- same as and for [[
  • && -- also same as [ EXPR1 -a EXPR2 ]
  • || -- same as or with [[
  • || -- also same as [ EXPR1 -o EXPR2 ]

Functions of Double Parentheses:

  • (( ... )) -- used primarily for number-based conditions
    • same as using let command
    • allows for the use of >= operators
    • allows for the use of && and ||
    • flag conditions not allowed
    • -a and -o also not allowed

Further Reading: