Branching and loops are an important construct in programming languages — they’re what makes computers keep grinding through work endlessly. Shell has corresponding syntax for them too.
In branching and loop constructs, the condition being tested matters a lot; in Shell, the test command can be used to test a condition.
1. The test Command
test is used in Shell for testing conditions — as the name suggests, it can run tests such as:
- whether two strings are equal
- whether a given path exists
- whether a path has various permissions
- …
The syntax is:
test condition
test $a -eq 5
If the judgment is logically true, it returns 0; if false, it returns 1. The return value can’t be captured with e=$(test $a -eq 5) — after that, e would be empty. Instead, use $?, which gets the return value of the previous command.
test 1 -eq 2
echo $? # output 1, which means false
test 1 -eq 1
echo $? # output 0, which means true
test’s return value can be used as the condition for a conditional (such as if else):
if test "${a}" -eq 5; then echo $a; fi
The test command can also be replaced with single square brackets ([]) — just note that when using square brackets, there must be a space between the bracket and the condition.
if [ -e ex ]; then echo "exist ex"; else echo "not exist ex"; fi
if test -e ex; then echo "exist ex"; else echo "not exist ex"; fi
1.1 Path checks
On Linux, files don’t necessarily have an extension, and there’s no difference between a file’s name and a directory’s name.
| Flag | Meaning |
|---|---|
-e | returns true if the path exists (file or directory), otherwise false — e is for exist |
-d | returns true if the path exists and is a directory, otherwise false — d is for directory |
-f | returns true if the path exists and is a file, otherwise false — f is for file |
-n | returns true if the path exists and its size is nonzero, otherwise false — n is for nonzero |
-z | returns true if the path exists and its size is zero, otherwise false — z is for zero |
-L/-h | returns true if the path exists and is a link, otherwise false — L is for link |
-r | returns true if the path exists and has read permission, otherwise false — r is for read |
-w | returns true if the path exists and has write permission, otherwise false — w is for write |
-x | returns true if the path exists and has execute permission, otherwise false — x is for execute |
if [ -e $a ]; then echo "exist $a"; fi
if [ -d $a ]; then echo "exist directory $a"; fi
if [ -f $a ]; then echo "exist file $a"; fi
if [ -n $a ]; then echo "$a is not None"; fi
if [ -z $a ]; then echo "$a is None"; fi
if [ -L $a ]; then echo "exist link $a"; fi
if [ -r $a ]; then echo "$a has read access"; fi
if [ -w $a ]; then echo "$a has write access"; fi
if [ -x $a ]; then echo "$a has execute access"; fi
1.2 Comparing files
The test command can also compare two files.
| Flag | Meaning |
|---|---|
patha -nt pathb | returns true if patha was modified more recently than pathb, otherwise false — nt is for newer than |
patha -ot pathb | returns true if patha was modified earlier than pathb, otherwise false — ot is for older than |
patha -ef pathb | returns true if patha and pathb share the same device and inode number, otherwise false |
if test $a -nt $b; then echo "$a is newer than $b"; fi
if test $a -ot $b; then echo "$a is older than $b"; fi
1.3 Comparing integers
The following operators are for comparing integers — if the input isn’t an integer, it errors out.
| Flag | Meaning |
|---|---|
-eq | returns true if the two integers are equal, otherwise false — eq is for equal; errors if the input isn’t two integers |
-gt | returns true if the first integer is greater than the second, otherwise false — gt is for greater than; errors if the input isn’t two integers |
\> | same meaning as -gt; needs to be escaped to distinguish it from the redirection operator |
-lt | lt is for less than |
\< | same as -lt |
-ge | ge is for greater equal — note there’s no \>= form |
-le | le is for less equal — note there’s no \<= form |
if test $a -eq $b; then echo "$a eqals $b"; fi
1.4 String (in)equality checks
= and != are for strings. Equal means the strings are exactly identical; even a small difference means unequal.
| Flag | Meaning |
|---|---|
= | returns true if the two strings match, otherwise false — note there must be spaces on both sides of the equals sign when comparing strings |
!= | returns true if the two strings differ, otherwise false |
if test $a = $b; then echo "$a eqals $b"; fi
2. Branching Structures
This is conditional logic — running different operations under different conditions. The common ones are if else, do while, and case esac.
2.1 The if else structure
if condition; then xxx; fi
if condition; then xxx; else yyy; fi
if condition_1; then xxx; elif condition_2;then yyy; else zzz; fi
2.2 The case esac structure
If there are many options, you could use a pile of if elif else, or you could use a case esac structure instead. This is the same as switch case in C. But what’s with ending in esac? Look at if ending in fi and it should click. 🐶
The syntax is:
case $x in
1) echo "select 1";;
2) echo "select 2";;
3) echo "select 3";;
*) echo "select nothing";;
esac
The final asterisk * means nothing else was matched — the default case. The double semicolon (;;) at the end of each branch means jumping out of the current case esac, similar to break in other languages. That said, shell scripts do have their own break and continue keywords, working the same way as in C.
3. Loop Structures
The common loop structures are for, while, and until:
for x in xxxx;
do
xyz_operation
done
while condition:
do
xyz
done
until condition
do
xyz
done
To run forever, while true can be used as an infinite loop.
continue jumps out of the current iteration and moves on to the next one. break jumps out of the whole loop.
Of course, both continue and break can also be followed by a number n, meaning they jump out of n levels of nested loops.
4. Conditional Logic Operations
4.1 Logical operators within a conditional
The result of an expression in Shell can be combined with logical AND, OR, and NOT:
| Flag | Meaning |
|---|---|
-a/&& | logical AND |
-o/|| | logical OR |
! | logical NOT |
if [ x -eq 5 ] && [ y -eq 6]; then echo "help"; fi
if test x -eq 5 && test y -eq 6; then echo "help"; fi
if ![ x -eq 5 ]; then echo "help"; fi
4.2 Chaining conditionals on commands
Commands can also be combined with logical operators.
command_1 && command_2
There’s a bonus here: if command_1 already returns false, command_2 doesn’t need to run at all.
if [ ! -d ${path1} ]; then
mdkir -p ${path1}
fi
The above can be shortened to:
[ ! -d ${path1} ] && mkdir -p ${path1}
Likewise, for OR, if the first condition is already satisfied, the rest won’t run. This style of writing can make code more concise.
5. Summary
This article first covered Shell’s test command, used for testing various conditions, then covered Shell’s branching and loop syntax, and chaining conditionals.