← Back to DevBytes

Zsh Scripting: Loops Complete Guide

Introduction to Zsh Loops

Zsh (Z shell) is a powerful shell that extends the Bourne shell syntax with modern features, better interactive usability, and richer scripting capabilities. Among its most useful scripting constructs are loops, which allow you to repeat blocks of code, iterate over collections, and automate repetitive tasks efficiently.

Loops matter because they are the backbone of automation. Whether you are processing files in a directory, parsing command output, or building CLI tools, loops let you write concise, maintainable scripts instead of duplicating code. Zsh supports several loop types — for, while, until, repeat, and select — each suited to different scenarios.

Why Loops Matter in Zsh Scripting

Loops reduce code duplication and make scripts easier to maintain. Instead of writing ten lines to process ten files, a single loop handles them all. They also enable dynamic behavior: your script adapts to runtime data such as file counts, user input, or API responses.

Zsh offers advantages over Bash when looping:

For Loops

Basic For Loop Over a List

The simplest form iterates over a space-separated list of items.

#!/usr/bin/env zsh

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

Iterating Over Arrays

Zsh arrays are first-class citizens. You can iterate cleanly without worrying about word splitting.

#!/usr/bin/env zsh

languages=(Python Rust Go TypeScript)

for lang in $languages; do
  echo "Language: $lang"
done

C-Style For Loops

For numeric ranges or counter-based iteration, Zsh supports C-style syntax.

#!/usr/bin/env zsh

for ((i = 1; i <= 5; i++)); do
  echo "Iteration $i"
done

Range Expansion

Zsh supports brace expansion for numeric ranges, which is concise and readable.

#!/usr/bin/env zsh

for n in {1..10}; do
  echo "Number: $n"
done

# With a step value
for n in {0..20..2}; do
  echo "Even: $n"
done

Looping Over Files with Globbing

Zsh's globbing makes file iteration powerful. Use **/* for recursive matching.

#!/usr/bin/env zsh

# All .zsh files in the current directory
for file in *.zsh; do
  echo "Found: $file"
done

# Recursive search
for file in **/*.md; do
  echo "Markdown file: $file"
done

While Loops

A while loop runs as long as a condition evaluates to true. It is ideal when the number of iterations is unknown in advance.

#!/usr/bin/env zsh

count=1
while (( count <= 5 )); do
  echo "Count is $count"
  (( count++ ))
done

Reading Lines from a File

Use while read to process a file line by line safely.

#!/usr/bin/env zsh

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

Reading Command Output

You can pipe command output directly into a while loop.

#!/usr/bin/env zsh

git status --short | while IFS= read -r line; do
  echo "Modified: $line"
done

Until Loops

The until loop is the inverse of while: it runs until a condition becomes true. This is useful for polling or waiting for a resource.

#!/usr/bin/env zsh

attempts=0
until ping -c 1 example.com &>/dev/null; do
  (( attempts++ ))
  echo "Attempt $attempts failed. Retrying..."
  sleep 2
  if (( attempts >= 5 )); then
    echo "Giving up."
    exit 1
  fi
done

echo "Connection established!"

The Repeat Loop (Zsh-Specific)

Zsh has a unique repeat construct for executing a command a fixed number of times. It is concise and elegant for simple repetition.

#!/usr/bin/env zsh

repeat 3 echo "Hello, Zsh!"

# With a compound command
repeat 5 {
  echo "Working..."
  sleep 1
}

Select Loops for Menus

The select loop creates interactive menus. It is perfect for CLI tools that need user input.

#!/usr/bin/env zsh

PS3="Choose an option: "
select option in "Start" "Stop" "Restart" "Quit"; do
  case $option in
    Start)   echo "Starting service..." ;;
    Stop)    echo "Stopping service..." ;;
    Restart) echo "Restarting service..." ;;
    Quit)    echo "Bye!"; break ;;
    *)       echo "Invalid choice." ;;
  esac
done

Loop Control: break and continue

Control the flow of loops with break (exit the loop) and continue (skip to the next iteration).

#!/usr/bin/env zsh

for n in {1..10}; do
  if (( n == 3 )); then
    continue  # Skip 3
  fi
  if (( n == 7 )); then
    break     # Stop at 7
  fi
  echo "Processing $n"
done

Breaking Nested Loops

Use break N to exit multiple loop levels.

#!/usr/bin/env zsh

for outer in 1 2 3; do
  for inner in a b c; do
    if [[ $outer == 2 && $inner == b ]]; then
      break 2
    fi
    echo "$outer-$inner"
  done
done

Practical Examples

Batch File Renaming

#!/usr/bin/env zsh

for file in *.jpg; do
  new_name="photo_${file:r:t}.jpg"
  mv "$file" "$new_name"
  echo "Renamed: $file -> $new_name"
done

Processing CSV Data

#!/usr/bin/env zsh

while IFS=, read -r name email role; do
  echo "Name: $name | Email: $email | Role: $role"
done < users.csv

Parallel Task Execution

#!/usr/bin/env zsh

urls=(
  "https://example.com/api/1"
  "https://example.com/api/2"
  "https://example.com/api/3"
)

for url in $urls; do
  curl -s "$url" > "${url##*/}.json" &
done
wait
echo "All downloads complete."

Best Practices

Process Substitution to Preserve Variables

#!/usr/bin/env zsh

total=0
while IFS= read -r num; do
  (( total += num ))
done < <(seq 1 100)

echo "Total: $total"

Conclusion

Loops are an essential tool in any Zsh scripter's toolkit. From simple list iteration with for to interactive menus with select, Zsh provides a rich set of looping constructs that are both expressive and efficient. By understanding when to use each loop type, applying proper quoting and arithmetic syntax, and following best practices like error handling and process substitution, you can write robust, maintainable scripts that scale with your automation needs. Start small, experiment with the examples above, and you will quickly find loops becoming second nature in your Zsh scripting workflow.

— Ad —

Google AdSense will appear here after approval

← Back to all articles