Bash Shell ScriptingI like to think of programming (scripting) as a series of decisions made based on a system of reasoning — a process otherwise known as logic.

Logic is something that we all make use of every day when we decide what to do, and how and in what order to do it. In such cases, logic is usually an unconscious process based on actions and consequences.

We employ more conscious forms of logic when solving problems and deliberately working through processes to achieve a desired result.

Programming (scripting) employs a very deliberate form of logic, brought to pass through the use of control structures, which are dependent on various conditions presented to them.

In life and programming alike, results can be controlled by whether or not conditions are met, and how and to what degree they are met. But I gotta tell you, it’s a lot easier to anticipate and control conditions in a program than it is in real life!

Types of Control Structures

There are 5 control structures used in shell scripting.

Each control structure has its own specific purpose; half the fun is determining which one will best fit each need.

Let’s jump into a brief description of each control structure.

if

Use if to perform actions only if specific conditions are met.

If can be as simple as if ... then ... fi or as comprehensive as if ... then ... elif ... then ... else ... then ... fi.

case

Use case to perform one of multiple specified actions depending on the value of the variable/etc.

for

Use for to perform actions in a loop for each item/number/etc. specified.

while

Use while to perform actions in a loop while a specified condition returns true.

until

Use until to perform actions in a loop until specified conditions return true.

Control Commands

Several commands exist that allow additional control to be exerted from within a controlled structure.

Most often these commands are used from within loops or functions (which we will get into later).

These control commands are:

break

Use break to terminate or break out of a loop when a condition is met.

continue

Use continue to transfer control to upcoming code, while continuing execution of the loop.

exit

Use exit to exit the entire shell script without finishing, if a condition is met (or not met), etc.

return

Use return to send back (return) results/code/data from within a function/etc.

Comparison Operators

Comparison operators are used by control structures to compare values; they will return TRUE or FALSE.

Operator Description
== Equal
>= Greater Than or Equal
> Greater Than
<= Less Than or Equal
< Less Than
!= Not Equal

Boolean Operators

Boolean operators are used by control structures to combine conditions and produce a more refined and logical TRUE/FALSE result.

Operator Description
&& Logical AND
|| Logical OR
! Logical NOT

Tests (Relational Operators)

test is a shell command that checks file types and compares values.

This command is very useful inside shell scripts when used to evaluate conditional expressions.

The test command can be used to determine if a file exists or is empty, what permissions the file has set, if a variables has a value, to compare the dates (ages) of two files, and to evaluate expressions, among other things.

The test operators will return either TRUE or FALSE. The available operators are:

Operator Description
-b FILE FILE exists and is block special
-c FILE FILE exists and is character special
-d FILE FILE exists and is a directory
-e FILE FILE exists
-f FILE FILE exists and is a regular file
-g FILE FILE exists and is set-group-ID
-G FILE FILE exists and is owned by the effective group ID
-h FILE FILE exists and is a symbolic link (same as -L)
-k FILE FILE exists and has its sticky bit set
-L FILE FILE exists and is a symbolic link (same as -h)
-O FILE FILE exists and is owned by the effective user ID
-p FILE FILE exists and is a named pipe
-r FILE FILE exists and read permission is granted
-s FILE FILE exists and has a size greater than zero
-S FILE FILE exists and is a socket
-t FD file descriptor FD is opened on a terminal
-u FILE FILE exists and its set-user-ID bit is set
-w FILE FILE exists and write permission is granted
-x FILE FILE exists and execute (or search) permission is granted
FILE1 -ef FILE2 FILE1 and FILE2 have the same device and inode numbers
FILE1 -nt FILE2 FILE1 is newer (modification date) than FILE2
FILE1 -ot FILE2 FILE1 is older than FILE2
( EXPRESSION ) EXPRESSION is true
! EXPRESSION EXPRESSION is false
EXPRESSION1 -a EXPRESSION2 both EXPRESSION1 and EXPRESSION2 are true
EXPRESSION1 -o EXPRESSION2 either EXPRESSION1 or EXPRESSION2 is true
-n STRING the length of STRING is nonzero
-z STRING the length of STRING is zero
STRING1 = STRING2 the strings are equal
STRING1 != STRING2 the strings are not equal
INTEGER1 -eq INTEGER2 INTEGER1 is equal to INTEGER2
INTEGER1 -ge INTEGER2 INTEGER1 is greater than or equal to INTEGER2
INTEGER1 -gt INTEGER2 INTEGER1 is greater than INTEGER2
INTEGER1 -le INTEGER2 INTEGER1 is less than or equal to INTEGER2
INTEGER1 -lt INTEGER2 INTEGER1 is less than INTEGER2
INTEGER1 -ne INTEGER2 INTEGER1 is not equal to INTEGER2

In coming lessons, these operators (tests) will begin to make more sense, as they are used in examples.

Conclusion

There’s a saying that “practice makes perfect”, but I have to assume that either that is entirely not true, or I don’t practice enough.

In any case, practice does help, so have some fun while you’re at it. I once wrote a shopping list for my sister in code, making heavy use of control structures, and she not only understood it, but she also followed the instructions to the letter, and had fun doing it!

In coming lesson we’ll go into details of how to make use of each control structure, complete with fully-functional examples that you can try out.