Thursday

19-06-2025 Vol 19

Day 30/ 30 Days of Linux Mastery: Mastering ‘for’, ‘do’, ‘while’, and ‘case’ Statements in Shell Scripts

Day 30/30 Days of Linux Mastery: Mastering ‘for’, ‘do’, ‘while’, and ‘case’ Statements in Shell Scripts

Welcome to Day 30 of our 30 Days of Linux Mastery series! Today, we’re tackling the core of shell scripting: control flow statements. Specifically, we’ll dive deep into for, do, while, and case statements. Mastering these constructs will empower you to write powerful, automated, and efficient shell scripts.

Why Learn Control Flow Statements?

Shell scripts without control flow are simply a list of commands executed sequentially. While useful, they lack the intelligence to adapt to different scenarios, iterate over data, or make decisions based on conditions. Control flow statements provide that intelligence, allowing your scripts to:

  • Automate Repetitive Tasks: Process multiple files, users, or servers without manual intervention.
  • Implement Conditional Logic: Execute different code blocks based on specific conditions (e.g., checking if a file exists, verifying a user’s input).
  • Create Dynamic Scripts: Adapt their behavior based on user input, system status, or external data.

Prerequisites

Before we begin, you should have a basic understanding of:

  • Basic Linux commands (ls, cd, echo, mkdir, etc.)
  • Shell scripting basics (creating and executing scripts, using variables)

If you’re new to shell scripting, we recommend revisiting the earlier days of this series.

1. The ‘for’ Loop: Iterating Over Lists and Ranges

The for loop is your go-to tool for iterating over a list of items. It’s incredibly versatile and widely used in shell scripting.

1.1. Basic ‘for’ Loop Syntax

The general syntax of a for loop is:

for variable in list
do
  # Commands to execute for each item in the list
done

Explanation:

  • for variable in list: This line defines the loop. variable is a variable that will hold each item from the list in each iteration. The list can be a space-separated list of values, a command substitution, or a variable containing a list.
  • do: Marks the beginning of the loop’s code block.
  • # Commands to execute...: This is where you put the commands you want to execute for each item in the list. You can access the current item using the $variable syntax.
  • done: Marks the end of the loop’s code block.

1.2. Examples of ‘for’ Loops

1.2.1. Iterating Over a List of Strings

#!/bin/bash

for fruit in apple banana cherry
do
  echo "I like $fruit"
done

Output:

I like apple
I like banana
I like cherry

1.2.2. Iterating Over a List of Files

#!/bin/bash

for file in *.txt
do
  echo "Processing file: $file"
  cat "$file" # Display the contents of the file
done

This script will iterate over all files ending with .txt in the current directory and print their names and contents.

1.2.3. Using Command Substitution to Generate the List

#!/bin/bash

for user in $(cut -d':' -f1 /etc/passwd)
do
  echo "User: $user"
done

This script uses cut to extract the usernames from the /etc/passwd file and then iterates over them.

1.2.4. Iterating Over a Range of Numbers

#!/bin/bash

for i in {1..5}
do
  echo "Number: $i"
done

Output:

Number: 1
Number: 2
Number: 3
Number: 4
Number: 5

You can also specify a step:

#!/bin/bash

for i in {1..10..2} # From 1 to 10, incrementing by 2
do
  echo "Number: $i"
done

Output:

Number: 1
Number: 3
Number: 5
Number: 7
Number: 9

1.3. Practical Example: Batch Renaming Files

Let’s create a script to rename all .txt files in a directory by adding a timestamp to their names.

#!/bin/bash

timestamp=$(date +%Y%m%d%H%M%S)

for file in *.txt
do
  new_name="${file%.txt}_${timestamp}.txt"
  mv "$file" "$new_name"
  echo "Renamed $file to $new_name"
done

Explanation:

  • timestamp=$(date +%Y%m%d%H%M%S): Gets the current date and time in a specific format.
  • new_name="${file%.txt}_${timestamp}.txt": Creates the new filename by removing the .txt extension, adding the timestamp, and then re-adding the .txt extension. ${file%.txt} is a parameter expansion that removes the shortest matching suffix (.txt) from the variable $file.
  • mv "$file" "$new_name": Renames the file.

2. The ‘while’ Loop: Repeating Until a Condition is False

The while loop executes a block of code repeatedly as long as a specified condition is true. It’s ideal for situations where you need to keep looping until a certain event occurs or a particular state is reached.

2.1. Basic ‘while’ Loop Syntax

The general syntax of a while loop is:

while condition
do
  # Commands to execute as long as the condition is true
done

Explanation:

  • while condition: The loop continues as long as the condition evaluates to true (exit code 0).
  • do: Marks the beginning of the loop’s code block.
  • # Commands to execute...: The commands within the loop are executed repeatedly.
  • done: Marks the end of the loop’s code block.

2.2. Examples of ‘while’ Loops

2.2.1. Simple Counter

#!/bin/bash

count=1

while [ $count -le 5 ]  # -le means "less than or equal to"
do
  echo "Count: $count"
  count=$((count + 1)) # Increment the counter
done

Output:

Count: 1
Count: 2
Count: 3
Count: 4
Count: 5

2.2.2. Reading Input from a File

#!/bin/bash

while IFS= read -r line
do
  echo "Line: $line"
done < input.txt

This script reads each line from the input.txt file and prints it. IFS= read -r line is a safe and reliable way to read lines from a file, even if they contain spaces or backslashes.

2.2.3. Waiting for a File to Exist

#!/bin/bash

file_to_wait_for="my_file.txt"

while [ ! -f "$file_to_wait_for" ] # ! -f means "file does not exist"
do
  echo "Waiting for $file_to_wait_for to exist..."
  sleep 5 # Wait for 5 seconds
done

echo "$file_to_wait_for has been created!"

This script waits until the file my_file.txt is created before continuing.

2.3. Practical Example: Monitoring System Load

Let's create a script to monitor system load and send an alert if it exceeds a threshold.

#!/bin/bash

load_threshold=2.0

while true # Loop indefinitely
do
  load_average=$(uptime | awk '{print $(NF-2)}')  # Get the 1-minute load average
  load_average=$(echo $load_average | tr -d ',') # Remove the comma

  if (( $(echo "$load_average > $load_threshold" | bc -l) )) ; then
    echo "Warning: Load average ($load_average) exceeds threshold ($load_threshold)!"
    # Add code to send an email or other alert here
  fi

  sleep 60 # Check every minute
done

Explanation:

  • load_average=$(uptime | awk '{print $(NF-2)}'): Gets the 1-minute load average from the output of the uptime command. awk '{print $(NF-2)}' extracts the second-to-last field (which is the load average).
  • load_average=$(echo $load_average | tr -d ','): Removes the comma from the load average string, if present.
  • if (( $(echo "$load_average > $load_threshold" | bc -l) )): Compares the load average to the threshold using bc for floating-point arithmetic.
  • sleep 60: Pauses the script for 60 seconds before checking the load average again.
  • while true : Creates an infinite loop, constantly monitoring system load. You might want to add a condition to break out of the loop after a certain number of iterations or based on another criteria.

3. The 'until' Loop: Repeating Until a Condition is True

The until loop is similar to the while loop, but it executes the block of code as long as the condition is false. It stops when the condition becomes true.

3.1. Basic 'until' Loop Syntax

The general syntax of an until loop is:

until condition
do
  # Commands to execute as long as the condition is false
done

3.2. Example: Waiting for a Process to Finish

#!/bin/bash

process_name="my_long_running_process"

until ! pgrep -x "$process_name" > /dev/null
do
  echo "Waiting for $process_name to finish..."
  sleep 5
done

echo "$process_name has finished!"

This script waits until a process named "my_long_running_process" is no longer running (as determined by pgrep) before continuing.

4. The 'case' Statement: Implementing Multi-Way Branching

The case statement allows you to execute different code blocks based on the value of a variable or expression. It's a cleaner and more readable alternative to using a long series of if/elif/else statements.

4.1. Basic 'case' Statement Syntax

The general syntax of a case statement is:

case variable in
  pattern1)
    # Commands to execute if variable matches pattern1
    ;;
  pattern2)
    # Commands to execute if variable matches pattern2
    ;;
  *) # Default case
    # Commands to execute if no other pattern matches
    ;;
esac

Explanation:

  • case variable in: Starts the case statement, specifying the variable to be evaluated.
  • pattern1), pattern2), etc.: These are the patterns to match against the value of the variable.
  • # Commands to execute...: The commands to be executed if the corresponding pattern matches.
  • ;;: Terminates the code block for each pattern. This is crucial; without it, execution will "fall through" to the next case.
  • *): The default case. This is executed if none of the other patterns match.
  • esac: Marks the end of the case statement (case spelled backwards).

4.2. Examples of 'case' Statements

4.2.1. Handling Different User Inputs

#!/bin/bash

read -p "Enter a command (start, stop, restart): " command

case "$command" in
  start)
    echo "Starting the service..."
    # Add code to start the service here
    ;;
  stop)
    echo "Stopping the service..."
    # Add code to stop the service here
    ;;
  restart)
    echo "Restarting the service..."
    # Add code to restart the service here
    ;;
  *)
    echo "Invalid command. Please use start, stop, or restart."
    ;;
esac

4.2.2. Checking File Type

#!/bin/bash

file="my_file.txt"

case "$(file --mime-type -b "$file")" in
  text/plain*)
    echo "$file is a text file."
    ;;
  image/*)
    echo "$file is an image file."
    ;;
  application/pdf)
    echo "$file is a PDF file."
    ;;
  *)
    echo "Unknown file type."
    ;;
esac

This script uses the file command to determine the MIME type of a file and then executes different code based on the type.

4.3. Using Wildcards in 'case' Statements

You can use wildcards in the patterns of a case statement to match multiple values.

#!/bin/bash

read -p "Enter a number (1-10): " number

case "$number" in
  [1-5])
    echo "The number is between 1 and 5."
    ;;
  [6-9]|10) #The | allows you to combine multiple patterns.
    echo "The number is between 6 and 10."
    ;;
  *)
    echo "Invalid number. Please enter a number between 1 and 10."
    ;;
esac

4.4. Practical Example: Creating a Simple Menu

Let's create a script that presents a menu to the user and executes different actions based on their choice.

#!/bin/bash

while true
do
  echo "Menu:"
  echo "1. List files"
  echo "2. Create directory"
  echo "3. Exit"
  read -p "Enter your choice: " choice

  case "$choice" in
    1)
      ls -l
      ;;
    2)
      read -p "Enter directory name: " dir_name
      mkdir "$dir_name"
      echo "Directory '$dir_name' created."
      ;;
    3)
      echo "Exiting..."
      exit 0
      ;;
    *)
      echo "Invalid choice. Please enter 1, 2, or 3."
      ;;
  esac
done

5. Combining Control Flow Statements

The real power of shell scripting comes from combining these control flow statements to create complex logic. You can nest for loops inside while loops, use case statements within for loops, and so on.

5.1. Example: Processing Files in Multiple Directories

#!/bin/bash

directories=("dir1" "dir2" "dir3")

for dir in "${directories[@]}" # Use "${array[@]}" to iterate over all elements of an array
do
  echo "Processing directory: $dir"
  cd "$dir"

  for file in *.txt
  do
    echo "  Processing file: $file"
    # Add code to process the file here
  done

  cd .. # Go back to the parent directory
done

5.2. Example: Using 'case' to Handle Options in a 'while' Loop

#!/bin/bash

while true
do
  read -p "Enter an option (a, b, c, or q to quit): " option

  case "$option" in
    a)
      echo "Option A selected."
      # Add code for option A here
      ;;
    b)
      echo "Option B selected."
      # Add code for option B here
      ;;
    c)
      echo "Option C selected."
      # Add code for option C here
      ;;
    q)
      echo "Quitting..."
      exit 0
      ;;
    *)
      echo "Invalid option. Please enter a, b, c, or q."
      ;;
  esac
done

6. Best Practices and Common Mistakes

6.1. Always Quote Variables

Always enclose variables in double quotes ("$variable") to prevent word splitting and globbing issues. This is especially important when dealing with filenames or paths that contain spaces.

6.2. Use 'set -x' for Debugging

The set -x command enables shell tracing, which prints each command to the console before it's executed. This is invaluable for debugging your scripts.

#!/bin/bash
set -x  # Enable tracing

# Your script code here

To disable tracing, use set +x.

6.3. Check Exit Codes

Every command returns an exit code (0 for success, non-zero for failure). You can check the exit code using the $? variable. This allows you to handle errors gracefully.

command
if [ $? -ne 0 ]; then
  echo "Error: Command failed."
  exit 1
fi

6.4. Be Careful with Infinite Loops

Make sure that your while and until loops have a condition that will eventually become false (for while) or true (for until) to avoid infinite loops. If you do create an infinite loop intentionally, ensure there's a mechanism (like a break statement or a signal handler) to exit the loop.

6.5. Use Meaningful Variable Names

Choose descriptive variable names to make your code more readable and understandable.

6.6. Indent Your Code

Proper indentation makes your code easier to read and understand. Use consistent indentation throughout your scripts.

7. Advanced Techniques

7.1. 'break' and 'continue' Statements

  • break: Exits the current loop prematurely.
  • continue: Skips the current iteration of the loop and proceeds to the next iteration.
#!/bin/bash

for i in {1..10}
do
  if [ $i -eq 5 ]; then
    continue # Skip iteration when i is 5
  fi
  if [ $i -eq 8 ]; then
    break    # Exit the loop when i is 8
  fi
  echo "Number: $i"
done

echo "Loop finished."

7.2. Nested Loops with 'break' and 'continue'

You can use labeled break and continue statements to control which loop is affected in nested loop scenarios (bash 4+).

#!/bin/bash

outer_loop: for i in {1..3}
do
  inner_loop: for j in {1..3}
  do
    if [ $i -eq 2 ] && [ $j -eq 2 ]; then
      continue outer_loop # Skip the rest of the outer loop's iteration
    fi
    echo "i=$i, j=$j"
  done
done

8. Conclusion

Today, we've covered the fundamental control flow statements in shell scripting: for, while, until, and case. Mastering these constructs is essential for writing powerful and automated scripts. Practice these concepts with different examples and scenarios to solidify your understanding. Continue experimenting, and you'll soon be writing sophisticated shell scripts with ease!

Congratulations on completing Day 30 of the 30 Days of Linux Mastery series! We hope you've found this journey valuable and that you're now equipped with the skills to confidently navigate the Linux command line and write effective shell scripts.

9. Further Exploration

  • Advanced Bash Scripting Guide: A comprehensive guide to all aspects of bash scripting.
  • tldp.org: The Linux Documentation Project, a wealth of information on Linux and shell scripting.
  • Online forums and communities: Stack Overflow, Reddit's r/linuxquestions, and other online forums are great places to ask questions and learn from other users.

```

omcoding

Leave a Reply

Your email address will not be published. Required fields are marked *