I 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.
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.
Use case to perform one of multiple specified actions depending on the value of the variable/etc.
Use for to perform actions in a loop for each item/number/etc. specified.
Use while to perform actions in a loop while a specified condition returns true.
Use until to perform actions in a loop until specified conditions return true.
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:
Use break to terminate or break out of a loop when a condition is met.
Use continue to transfer control to upcoming code, while continuing execution of the loop.
Use exit to exit the entire shell script without finishing, if a condition is met (or not met), etc.
Use return to send back (return) results/code/data from within a function/etc.
Comparison operators are used by control structures to compare values; they will return TRUE or FALSE.
|>=||Greater Than or Equal|
|<=||Less Than or Equal|
Boolean operators are used by control structures to combine conditions and produce a more refined and logical TRUE/FALSE result.
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:
|-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.
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.