Introduction to Tools and Techniques in Computer Science

Structures: conditional statements and loops

Franklin Bristow

Structures: conditional statements and loops

Shell scripting languages are fully featured programming languages and include structures like conditional statements and loops. They contain other structures, too, but let’s stick to the basics.

Conditional statements

Conditional statements in bash use the familiar if keyword and resemble the expressions you’ve seen in other languages.

One of the major differences in bash are expressions themselves: most of the questions you’re going to be asking about a variable use unary operators.

Here’s what a bash conditional statement looks like:

if [[ -a hello.c ]]; then
    echo "hello.c exists"
else
    echo "hello.c does not exist"
fi

The -a is a unary operator on file names. -a returns true if the file exists, and returns false if the file does not exist.

Spacing is important here! bash is not a very smart language. You might be tempted to leave out spaces between [[ and -a or between hello.c and ]], but you must have spaces between these symbols.

WHY?!

bash is, uh, weird. [[ is technically a command that takes arguments. The arguments the [[ command is getting in the above example are -a, hello.c, and ]]. The ; is a line separator (like in Python it’s optional, but can be used).

Yeah, weird.

bash has many unary operators that you can use to test files or variables. The one we care about right now is the -n operator, asking if a string is non-zero in length.

Let’s add a conditional statement to la to test for the presence of arguments:

#!/usr/bin/env bash

if [[ -n "$1" ]] ; then
    ls -al "$1"
else
    ls -al
fi

You can find more unary operators in bash by reading the CONDITIONAL EXPRESSIONS section of its manual page, but here are some examples:

Operator Meaning
-a file True if file exists.
-d file True if file exists and is a directory.
-r file True if file exists and is readable.
-s file True if file exists and has a size greater than 0.
string1 == string2 True if the strings are equal.

Loops

We can’t talk about conditional statements without at least saying something about loops!

Similar to conditional statements, loops use the familiar for keyword. Bash also supports while and until loops, but most of the time you’re using a loop in Bash, you’re operating on some sequence of file names rather than until some event happens.

The structure of a for loop is frustratingly different from conditional statements in a way it’s not in other programming languages — the conditional statements you saw above use the [[ and ]] brackets for wrapping the expression, but for loops generally do not use brackets or parenthesis in Bash.

Here’s what a for loop looks like in Bash:

for f in * ; do
    echo $f
done
  • The for is … for, it’s the start of the loop.
  • The f is the name of the variable you want to use as the name for the value in the current iteration of the loop over the sequence.
  • The in is a separator between the variable name and the sequence.
  • In this case * is the sequence. This is a “glob” or a pattern, and this glob in the shell means “all files in the current directory” (more on this soon!).
  • The semi-colon ;, like in conditional statements above, ends the current statement.
  • do, then is the beginning of the body of the loop.
    • echo $f is one command you want run on the variable. This will print out the variable’s name.
  • done ends the body of the loop.

Maybe this looks sort of familiar. Maybe this looks like what we were doing with find and -exec. They do accomplish similar results!

Both work, and both are effective. One way to think about this matching of ideas is that find and -exec are more of a functional programming paradigm (this is a map operation), for loops are more of a procedural paradigm.

Some people may find that a loop is more readable than find and -exec, though, especially if you’re going to do several operations on a file or item in a loop.

When you’re writing for loops, the sequence can either use the patterns you’ve seen before (like *.md), or can be the result of a command.

In fact, we can rewrite the for loop from above using find!

for f in $(find . -maxdepth 1) ; do
    echo $f
done

The output looks a little bit different, but the result is the same.

Another common kind of loop you may want to write is one that iterates over a sequence of numbers (like the traditional for loop you’ve seen in languages like Java). To do that you can use the seq command:

for num in $(seq 1 10) ; do
    echo $num
done