| |

LFCS 123 ๐Ÿง Shell Scripting โ€” Conditionals

โฑ๏ธ Estimated read: 10 min | |

The previous chapter covered special parameters: positional arguments and exit codes. This chapter focuses on conditionals: if, elif, else, case, and the test operators that determine which branch runs. The LFCS exam tests the syntax of the if statement, the difference between [ and [[, and the distinction between numeric comparison and string comparison. The critical trap is the unquoted variable in a test: [ $var = "value" ] fails if $var is empty because the test becomes [ = "value" ], which is a syntax error. The fix is to quote the variable: [ "$var" = "value" ].

Key point: The if statement evaluates the exit code of the command it runs. A zero exit code means true. Any non-zero value means false. The [ command (also called test) is a builtin. The [[ keyword is a bash extension with more features: pattern matching with =~, regex matching with =~, and no word splitting. The (( ... )) form is for arithmetic conditions. The case statement matches patterns, not exit codes.


Why conditionals require careful syntax

The exit code problem. The shell does not have boolean values. The if statement evaluates the exit code of the command. The [ command returns 0 (true) if the condition is satisfied, 1 (false) if not, and 2 (error) if the syntax is wrong. The [[ keyword behaves similarly but has fewer error cases. The (( form returns 0 if the arithmetic result is non-zero, 1 if zero.

The test operator problem. The [ command accepts operators for string comparison (=, !=, -z, -n), numeric comparison (-eq, -ne, -lt, -le, -gt, -ge), and file tests (-f, -d, -e, -r, -w, -x, -s, -L). The = operator is for strings. The -eq operator is for numbers. The [ 10 = 10 ] is a string comparison. The [ 10 -eq 10 ] is a numeric comparison. The two produce the same result for this input, but for [ 010 = 10 ] they differ: string comparison is false, numeric comparison is true.

The quoting problem. The unquoted variable in a test is subject to word splitting. If the variable is empty, the test becomes [ = "value" ], which is a syntax error. The fix is to quote the variable: [ "$var" = "value" ]. The [[ form does not require quoting because it does not perform word splitting, but quoting is still a good habit.

The [[ versus [ problem. The [[ keyword is a bash extension. It supports pattern matching ([[ $name == J* ]]), regex matching ([[ $email =~ ^[a-z]+@[a-z]+\.[a-z]+$ ]]), and logical operators without the escaping required by [. The [ command is POSIX and works in any shell. For portability, use [. For bash-specific features, use [[.

The trade-off. The [ command is portable but has more edge cases. The [[ keyword is safer but bash-only. The (( form is for arithmetic only. The case statement is for pattern matching, which is more readable than a chain of if statements with string comparisons.


a. The if, elif, and else structure

The if statement evaluates a command. If the exit code is zero, the then block runs. If not, the elif block is evaluated. If no condition is true, the else block runs.

#!/bin/bash
if [ "$1" -gt 10 ]; then
  echo "Greater than 10"
elif [ "$1" -eq 10 ]; then
  echo "Equal to 10"
else
  echo "Less than 10"
fi

The then keyword can be on the same line as the condition if it is preceded by a semicolon, or on the next line if the condition is on its own line. The fi keyword closes the if block.

The test command can be [, [[, ((, or any command that returns an exit code. The if grep -q "pattern" file; then form runs grep and uses its exit code. A successful grep returns 0, which is true.

if grep -q "TODO" main.c; then
  echo "TODO found"
fi

b. String, numeric, and file test operators

The test operators are divided into three categories: string, numeric, and file.

# String comparison
if [ "$name" = "alice" ]; then
  echo "Hello, alice"
fi

if [ -z "$name" ]; then
  echo "Name is empty"
fi

if [ -n "$name" ]; then
  echo "Name is not empty"
fi

The = operator is for string equality. The != is for inequality. The -z is true if the string is empty. The -n is true if the string is not empty.

# Numeric comparison
if [ "$count" -eq 0 ]; then
  echo "Count is zero"
fi

if [ "$count" -lt 10 ]; then
  echo "Count is less than 10"
fi

The numeric operators are -eq (equal), -ne (not equal), -lt (less than), -le (less than or equal), -gt (greater than), -ge (greater than or equal). They require integer operands. The shell returns an error if the operands are not numeric.

# File tests
if [ -f "$file" ]; then
  echo "File exists"
fi

if [ -d "$dir" ]; then
  echo "Directory exists"
fi

if [ -x "$script" ]; then
  echo "Script is executable"
fi

The file tests are -e (exists), -f (regular file), -d (directory), -r (readable), -w (writable), -x (executable), -s (non-empty), -L (symbolic link).


c. The [[ keyword, arithmetic, and case

The [[ keyword supports pattern matching and regex matching. It does not perform word splitting, so quoting is optional but still recommended.

# Pattern matching
if [[ "$name" == J* ]]; then
  echo "Name starts with J"
fi

# Regex matching
if [[ "$email" =~ ^[a-z]+@[a-z]+\.[a-z]+$ ]]; then
  echo "Valid email"
fi

The =~ operator matches the left side against the regular expression on the right side. The match is unanchored unless ^ and $ are used. The matched substring is available in BASH_REMATCH.

The (( form is for arithmetic conditions. It returns 0 if the expression is non-zero, 1 if zero.

if (( count > 10 )); then
  echo "Count is greater than 10"
fi

The case statement matches the value against patterns. It is the correct choice when there are many possible values for a single variable.

case "$1" in
  start)
    echo "Starting"
    ;;
  stop)
    echo "Stopping"
    ;;
  restart)
    echo "Restarting"
    ;;
  *)
    echo "Usage: $0 {start|stop|restart}"
    exit 1
    ;;
esac

The * pattern matches any value and is the fallback. The ;; terminates each branch. The ;& and ;;& variants are bash extensions for fallthrough and continued matching.


Complete Example Session

# ============================================
# PART 1: BASIC IF
# ============================================

cat > check.sh <<'EOF'
#!/bin/bash
if [ "$1" -gt 10 ]; then
  echo "Greater than 10"
fi
EOF
chmod +x check.sh
./check.sh 15
# Output: Greater than 10
# ============================================
# PART 2: IF-ELIF-ELSE
# ============================================

cat > classify.sh <<'EOF'
#!/bin/bash
if [ "$1" -gt 10 ]; then
  echo "Large"
elif [ "$1" -eq 10 ]; then
  echo "Exact"
else
  echo "Small"
fi
EOF
chmod +x classify.sh
./classify.sh 5
# Output: Small
# ============================================
# PART 3: STRING TEST
# ============================================

cat > greet.sh <<'EOF'
#!/bin/bash
if [ "$1" = "alice" ]; then
  echo "Hello, Alice"
else
  echo "Hello, stranger"
fi
EOF
chmod +x greet.sh
./greet.sh alice
# Output: Hello, Alice
# ============================================
# PART 4: EMPTY VARIABLE
# ============================================

cat > empty.sh <<'EOF'
#!/bin/bash
if [ -z "$1" ]; then
  echo "No argument"
fi
EOF
chmod +x empty.sh
./empty.sh
# Output: No argument
# ============================================
# PART 5: FILE TEST
# ============================================

cat > filecheck.sh <<'EOF'
#!/bin/bash
if [ -f "$1" ]; then
  echo "File exists"
else
  echo "File not found"
fi
EOF
chmod +x filecheck.sh
./filecheck.sh /etc/passwd
# Output: File exists
# ============================================
# PART 6: [[ PATTERN
# ============================================

cat > pattern.sh <<'EOF'
#!/bin/bash
if [[ "$1" == J* ]]; then
  echo "Starts with J"
fi
EOF
chmod +x pattern.sh
./pattern.sh James
# Output: Starts with J
# ============================================
# PART 7: REGEX
# ============================================

cat > regex.sh <<'EOF'
#!/bin/bash
if [[ "$1" =~ ^[a-z]+@[a-z]+\.[a-z]+$ ]]; then
  echo "Valid email"
fi
EOF
chmod +x regex.sh
./regex.sh test@example.com
# Output: Valid email
# ============================================
# PART 8: ARITHMETIC
# ============================================

cat > arith.sh <<'EOF'
#!/bin/bash
if (( $1 > 10 )); then
  echo "Greater than 10"
fi
EOF
chmod +x arith.sh
./arith.sh 15
# Output: Greater than 10
# ============================================
# PART 9: CASE
# ============================================

cat > case.sh <<'EOF'
#!/bin/bash
case "$1" in
  start)  echo "Starting" ;;
  stop)   echo "Stopping" ;;
  *)      echo "Usage: $0 {start|stop}" ;;
esac
EOF
chmod +x case.sh
./case.sh start
# Output: Starting

The nine parts trace the conditionals: basic if, elif/else, string test, empty variable, file test, [[ pattern, regex, arithmetic, and case.


Quick Reference

Conditionals

SyntaxPurpose
if cmd; then ...; fiBasic conditional
if ...; then ...; elif ...; then ...; else ...; fiMulti-branch
case $var in pattern) ... ;; esacPattern match

String Tests

OperatorMeaning
=Equal
!=Not equal
-zEmpty
-nNon-empty

Numeric Tests

OperatorMeaning
-eqEqual
-neNot equal
-ltLess than
-leLess than or equal
-gtGreater than
-geGreater than or equal

File Tests

OperatorMeaning
-eExists
-fRegular file
-dDirectory
-rReadable
-wWritable
-xExecutable
-sNon-empty
-LSymlink

[[ Operators

OperatorMeaning
==Pattern match
=~Regex match
&&Logical AND
`

Best Practices

โœ… Do This:

if [ "$var" = "value" ]; then
<!-- Quote the variable -->                          # โœ…
if [[ "$name" =~ ^[A-Z] ]]; then
<!-- Use [[ for regex and patterns -->              # โœ…
case "$1" in
  start) ... ;;
  *) ... ;;
esac
<!-- Use case for multi-value matching -->          # โœ…

โŒ Don’t Do This:

if [ $var = "value" ]; then
<!-- Fails if var is empty -->                       # โŒ
if [ "$a" > "$b" ]; then
# For numbers
<!-- > is string comparison -->                      # โŒ
if (($var == "string")); then
<!-- (( )) is arithmetic only -->                     # โŒ

Common Pitfalls

PitfallWhy It HappensFix
unary operator expectedUnquoted empty variable"$var"
String comparison failsUsed -eq for stringsUse =
Numeric comparison failsUsed = for numbersUse -eq
[[ syntax errorUsing in /bin/shUse bash or [
Case falls throughMissing ;;Add ;;

Real-World Examples

1. Basic If

if [ "$1" = "yes" ]; then echo "Confirmed"; fi

2. If-Else

if [ -f "$file" ]; then echo "Found"; else echo "Missing"; fi

3. Elif

if [ "$n" -gt 10 ]; then echo "Big"; elif [ "$n" -eq 10 ]; then echo "Exact"; else echo "Small"; fi

4. Empty Check

if [ -z "$var" ]; then echo "Empty"; fi

5. File Check

if [ -x /usr/bin/env ]; then echo "Executable"; fi

6. Pattern Match

if [[ "$name" == J* ]]; then echo "Starts with J"; fi

7. Regex Match

if [[ "$ip" =~ ^[0-9]+\.[0-9]+\. ]]; then echo "IP"; fi

8. Arithmetic

if (( count > 0 )); then echo "Positive"; fi

9. Case

case "$1" in start) echo "Start" ;; stop) echo "Stop" ;; esac

10. Combined Test

if [ -f "$file" ] && [ -r "$file" ]; then echo "Readable file"; fi

Visual

if Execution

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  if [ condition ]; then                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Exit code of [ condition ]                  โ”‚
โ”‚  โ”œโ”€โ”€ 0 (true) โ†’ then block                   โ”‚
โ”‚  โ””โ”€โ”€ non-zero โ†’ elif/else                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  fi                                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

[ vs [[

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  [ ]  (POSIX test)                           โ”‚
โ”‚  โ”œโ”€โ”€ Word splitting applies                  โ”‚
โ”‚  โ”œโ”€โ”€ Requires quoting                        โ”‚
โ”‚  โ”œโ”€โ”€ No regex                                โ”‚
โ”‚  โ””โ”€โ”€ No pattern matching                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  [[ ]]  (bash keyword)                       โ”‚
โ”‚  โ”œโ”€โ”€ No word splitting                       โ”‚
โ”‚  โ”œโ”€โ”€ Regex with =~                           โ”‚
โ”‚  โ”œโ”€โ”€ Pattern with ==                         โ”‚
โ”‚  โ””โ”€โ”€ Logical && and ||                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Condition evaluationExit code (0 = true)
POSIX test[
Bash test[[
String equality=
Numeric equality-eq
Empty check-z
Non-empty check-n
File exists-e
Regular file-f
Directory-d
Regex match=~
Arithmetic(( ))
Pattern matchcase

Key takeaways:

  • The if statement evaluates exit codes. A zero exit code means true. A non-zero exit code means false. The [ command returns 0 if the condition is true, 1 if false.
  • The [ command requires quoting. An unquoted empty variable becomes a syntax error. The "$var" form is the fix. The [[ keyword does not perform word splitting, so quoting is optional but recommended.
  • String and numeric comparisons use different operators. The = operator is for strings. The -eq operator is for numbers. The [ "$a" = "$b" ] and [ "$a" -eq "$b" ] are different tests.
  • The [[ keyword supports regex and patterns. The =~ operator is for regular expressions. The == operator with a wildcard is for pattern matching. The && and || operators are logical combinations.
  • The case statement is for multi-value matching. It is more readable than a chain of if/elif statements with string comparisons. The * pattern is the fallback.

Remember: The conditional is the gate. The test operator is the check. The exit code is the result. The if statement evaluates the exit code. The [ and [[ commands return exit codes. The (( form returns exit codes. The case statement matches patterns, not exit codes. The exam tests the syntax and the difference between the operators.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!