 davelab
 GitHub

Functions and Conditional Logic

Conditionals

The “if” Command

To do a simple “if/else” conditional statement, we use the if command. You may be able to tell from the syntax that it is a shell command (actually a sequence of commands).

if CONDITION; then COMMAND; fi

In script files, it’s usually written out like this:

if CONDITION; then
  COMMAND
fi

The “CONDITION” is actually another command to run, and the return code of that command is the boolean value that is evaluated. If the command ran without error, and returned a code of “0”, then the statement is “true” and the then block will execute.

So the purpose of an if statement is to conditionally execute a command based on whether another command succeeds or fails.

You can also use else and elif clauses:

if ls /tmp/folder1; then
  echo "Found /tmp/folder1"
elif /tmp/folder2; then
  echo "Found /tmp/folder2"
else
  echo "Did not find either folder1 or folder2"
fi

Test Operators

If the if statement is based on the exit code of a command, then how do you check other kinds of conditions, like if two strings are equal, one number is greater than another number, or if a string is empty?

For this, you need to use a “test operator”. Basically, a special command that can compare one or more terms and translate some conditional logic into a return code that the if statement can use.

A test operator is written in the form of [[ ]] with your condition between the inner brackets.

You may see other examples using single-brackets “[ ]”. The double-brackets are an extension added by bash that add more functionality over the standard single-bracket syntax. Always use the double-brackets.

If you want to compare two numbers, use -gt to see if one is greater:

if [[ $x -gt 0 ]]; then
  echo "$x > 0"
fi

If you want to check if a string is empty or not, use -z:

if [[ -z $x ]]; then
  echo "\$x is empty."
else
  echo "\$x is a valid string."
fi

To see how a test works, you can try executing one directly in the shell. Use echo $? to print the return code of the previous command to see if the result was “true”/”false”.

[[ 1 -gt 0 ]]
echo $?
# 0  (remember, this means 'true')

[[ 1 -gt 2 ]]
echo $?
# 1  ('false' - non-zero return code)

Remember, these boolean conditions are based on the return code of the command. A return code of “0” means there was no error. A non-zero return code means there was an error (or “failure”). So “true” = 0, and “false” = !0.

It’s a confusing point, but it rarely comes up because the boolean operators take care of everything. The only time you see an integer value for the status is when you manually inspect “$?”.

To see a list of all test operators, run man bash and search for “CONDITIONAL EXPRESSIONS” to see the list.

Here are a few common ones:

Operator Description Example
-eq Equals [[ $x -eq 0 ]]
= Equals (for strings) [[ $x = "dave" ]]
-ne Not Equal [[ $? -ne 0 ]]
!= Not Equal (for strings) [[ $x != "true" ]]
-lt Less Than [[ $x -lt 20 ]]
-le Less Than or Equal [[ $x -le 3 ]]
-gt Greater Than [[ $x -gt 0 ]]
-ge Greater Than or Equal [[ $x -ge 10 ]]
-z The string is empty/null [[ -z $empty ]]
-n The string is not empty/null [[ -n $string ]]
-e Check if file exists at given path [[ -e /tmp/test ]]
-f Check if the target is a file [[ -f /tmp/test.txt ]]
-d Check if the target is a directory [[ -d ~/source ]]

The “case” Command

The case statement allows you to evaluate a condition, and then supply a list of values to match on the result and the commands that should be executed for each match.

case "$x" in
    "")     echo "Nothing specified." ;;
    hello)  echo "Hello world." ;;
    bye)    echo "Goodbye." ;;
    *)      echo "I didn't understand that." ;;
esac

In this example, we’re evaluating the value of the x variable and matching it against a few specific target values. But you could substitute any command by using "$( ... )" to capture it’s output as the case pattern.

case "$(head -n1 /tmp/logfile)" in
# ...

Note that we are wrapping the variable/command output in double-quotes so it doesn’t break if the value contains spaces or other reserved symbols.

The resulting value from the case command will be checked against each pattern in the list.

The ;; at the end is important. It signals the end of the section that handles one match. It also signals the case statement to stop scanning for additional matches (it does not “fall-through”).

If you need to write multiple commands in one match section, use ; or |, etc. to separate commands:

    "")     echo "Nothing specified."; exit 1 ;;

This would print the message and then exit with a non-zero return code, indicating that the command was not successful.

If you want the case statement to also execute the next match block after this, you can end the block with a ;&:

case "$x" in
    hello)  echo "Hello world." ;&
    bye)    echo "Goodbye." ;;
esac

In this version, if $x resolves to “hello”, then it will echo “Hello world.” and then continue to print “Goodbye.” also. If $x resolves to “bye”, then it just prints “Goodbye.”

And if you want the case statement to continue scanning for more matches after completing this block, use ;| instead.

case "$x" in
    h*)       echo "Starts with 'h'" ;|
    hello)    echo "Hello!" ;;
esac

If $x resolves to “hello”, then it will print both messages. If it resolves to any other value that starts with “s”, it will only print the first message.

Functions

Functions allow you to group commands and logic into reusable components that work like any other command or shell script. This means a function call sets a return status and works with the standard I/O streams.

Syntax

my_func () {
  echo "hello world"
}

The parentheses are always written “empty” () - you don’t actually list your arguments there.

Arguments

A function gets its arguments using the same variables used for the script parameters: $@, $1, $2, …

my_func () {
  echo "$1"

  # default values work just like script defaults
  echo "${3:-'default'}"
}

This does mean you can’t directly access script arguments from within a function.

Return

The return command doesn’t return a specific value back to the caller of your function. It actually sets the value of $?. It’s the return code for a function.

This allows functions to work just like any other command or script. They can work with streams and logical operators.

So how do you actually return a value to the caller? You put it on the output stream.

my_func () {
  echo "echo writes a string to the output stream"
}

output="$(my_func)"
echo "$output"
# echo writes a string to the output stream

Global and Local Variables

Variables defined in your script files are global (to the entire script file) by default. To make a local-only variable inside of your function, use local:

GLOBAL_VAR="GLOBAL"

my_func () {
  local local_var="local"

  echo "$GLOBAL_VAR $local_var"
}

NEXT »