Right, let’s talk about loops. Because if you’re going to be telling this computer what to do, you’ll inevitably need to tell it to do the same thing over and over again. That’s the whole point of scripting, and loops are how we avoid copying and pasting the same line of code fifty times. It’s automation 101, and bash gives you a few solid, if occasionally quirky, ways to get it done.

The for Loop: Your Workhorse

The for loop is your go-to when you know what you want to iterate over, or at least, you know how to get a list of those things. Its syntax is refreshingly straightforward. You give it a list of items, and it assigns each one to a variable, runs the code block, and moves on to the next.

for villain in Loki Magneto Syndrome Dr_Doom; do
  echo "Avengers, assemble! We're facing $villain!"
done

But here’s where it gets powerful: you rarely type the list out manually. You use a command substitution or a filename expansion (globbing).

# Loop through all .txt files in the current directory
for file in *.txt; do
  echo "Processing file: $file"
  # maybe run some sed or awk magic on it here
done

# Loop through the output of a command (one word per line)
for word in $(cat wordlist.txt); do
  echo "Checking: $word"
  # Imagine a curl command here to check a login form... for a friend.
done

Pitfall Alert: That last example is a classic footgun. If any element in your list has a space in it (e.g., “Doctor Doom”), the for loop will treat it as two separate items: “Doctor” and “Doom”. It’s not the loop’s fault; it’s how bash handles word splitting. If your data might have spaces, you need a different strategy, often involving read and Internal Field Separators (IFS), which is a whole other can of worms we’ll open later.

The while Loop: The “Until Something Changes” Loop

Use while when you want to keep looping as long as a certain condition is true. This is your loop for reading files line-by-line, waiting for a process to finish, or creating an infinite loop when you accidentally make the condition always true (we’ve all done it).

The most canonical example is reading a file properly, without the word-splitting nonsense of a for loop:

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

Let’s be honest, that IFS= read -r line incantation looks like gibberish. Here’s the why:

  • IFS= (Internal Field Separator set to nothing) prevents leading/trailing whitespace from being trimmed.
  • -r prevents backslash escapes from being interpreted.
  • line is the variable name we’re using. This is the robust, professional way to read a file line-by-line in bash. Memorize it.

You can also use while with a command’s success/failure. The loop runs as long as the command exits successfully (exit code 0).

# Keep checking if a server is up until it responds
while ! ping -c 1 myserver.com > /dev/null 2>&1; do
  echo "Server is down. Checking again in 5 sec..."
  sleep 5
done
echo "Server is back up!"

The until Loop: The “While’s Evil Twin”

The until loop is just while with the condition inverted. It loops until a condition becomes true. It’s less common because you can usually just rephrase the logic with while, but it can make for more readable code sometimes.

# This does the exact same thing as the previous while example
until ping -c 1 myserver.com > /dev/null 2>&1; do
  echo "Server is down. Checking again in 5 sec..."
  sleep 5
done
echo "Server is back up!"

Whether you use while ! or until is a matter of which reads more clearly to you. I find while ! (“while this is not true”) more explicit, but until (“until this is true”) has its place.

break and continue: Your Emergency Exits

These are your loop control operators, and they work exactly as you’d hope.

  • break: Immediately exit the loop. No more iterations. You’re done here.
  • continue: Skip the rest of the commands in this iteration and jump right to the next one.
# Look for a specific file, then stop looking
for file in /some/dir/*; do
  if [[ "$file" == *"secret_recipe.txt"* ]]; then
    echo "Found it! $file"
    break # Stop the loop entirely. We got what we came for.
  fi
  echo "Still looking... found $file"
done

# Process only files that are actually readable
for file in *; do
  if [[ ! -r "$file" ]]; then
    echo "Skipping unreadable file: $file" >&2
    continue # Jump to the next file in the list
  fi
  echo "Doing something very important with $file"
done

You can even use break n to break out of n levels of nested loops, which is a clever trick that I almost never see in the wild because nested loops in bash can get messy fast.

The key with all of this is to remember that bash isn’t Python. It’s old, it’s a bit janky, and its loops are built for processing streams of text. Respect that, understand its quirks (like the word splitting), and you’ll turn these simple constructs into incredibly powerful automation tools. Now go make your computer do the boring work. It’s what it’s for.