Skip to content

Bash For Loop Examples

bashloopsforscriptingbeginner
2 min read

Quick Answer

A bash for loop repeats a block of code once per item in a list. The safe forms are: loop a glob directly (for f in *.log) to iterate files, which handles spaces and special characters correctly; loop a brace range (for i in {1..10}) or a C-style counter (for ((i=0; i<10; i++))) for numbers; and loop an array with the quoted expansion (for x in "${items[@]}") so each element stays intact. The form to avoid is for f in $(ls) — command substitution word-splits on whitespace, so a file named report final.txt becomes two loop iterations, report and final.txt, and the real file is never touched. Always quote the loop variable when you use it ("$f"), because the split happens at use, not only at the loop header. Glob over parse, quote on use, and the loop stops surprising you.

The loop that backed up nothing for three weeks

A nightly job looped for f in $(ls /data/exports) and copied each file to a backup volume. It ran green every night. Three weeks later we needed the Q3 final.xlsx export and it was not in the backup — not the current one, not any of the twenty-one before it. The loop had been splitting Q3 final.xlsx into Q3 and final.xlsx, trying to copy two paths that did not exist, logging two harmless "no such file" lines, and moving on. The files without spaces backed up fine, which is exactly why nobody caught it.

The bug was not cp. It was looping over parsed command output instead of a glob.

Loop a glob — the form for files

bash
#!/bin/bash # Script: backup-exports.sh # Purpose: copy export files to a backup dir without losing ones with spaces # Usage: ./backup-exports.sh set -euo pipefail CHECK="✓" CROSS="✗" SRC_DIR="/data/exports" DEST_DIR="/backup/exports" shopt -s nullglob # an empty match expands to nothing, not the literal pattern for f in "$SRC_DIR"/*.xlsx; do if cp -- "$f" "$DEST_DIR/"; then echo "$CHECK backed up: $(basename "$f")" else echo "$CROSS failed: $(basename "$f")" fi done

for f in "$SRC_DIR"/*.xlsx lets bash expand the glob itself, which produces a correctly-separated list no matter what is in the names. Quoting "$f" on use is the other half — the split that bit us happens when the variable is used, not only in the loop header. The -- tells cp to stop reading options, so a file named -rf cannot turn into a flag.

Loop a range or a counter — the form for numbers

bash
# Fixed count, known at parse time for i in {1..5}; do echo "$CHECK attempt $i" done # Variable bound — brace ranges DON'T expand variables, so use C-style attempts=5 for ((i = 1; i <= attempts; i++)); do echo "$CHECK attempt $i of $attempts" done

{1..5} is fixed before variables expand, so {1..$attempts} produces the literal string {1..5}-style garbage, not a range. The moment your bound is a variable, the C-style for (( ... )) is the correct tool.

Loop an array — quote the expansion

bash
servers=("web-01" "db primary" "cache-02") for s in "${servers[@]}"; do echo "$CHECK checking: $s" done

"${servers[@]}" with quotes and [@] keeps db primary as one element. Drop the quotes and you are back to word-splitting — the same bug, new disguise.

The rule across all of these: glob over parse, quote on use. For reading a file's lines one at a time, a for loop is the wrong tool — see read a file line by line. And wrap any loop that touches real files in set -euo pipefail so a failure stops the script instead of looping past it.

BashSnippets logo

Written by Anguishe

Creator of BashSnippets.xyz

bashsnippets.xyz/about

Run this script on a real Linux server

Get $200 free credit — DigitalOcean

Get $200 Free →

Affiliate link · we earn a commission

Need a domain for your next project?

Register with Namecheap — free WHOIS privacy included

Check Domain Prices →

Affiliate link · we earn a commission

PAID RESOURCE — $9

The Production Bash Toolkit

6 scripts + shared library + 52-page field guide. The production layer the free snippets don't cover.

Get the Toolkit →

Related Snippets

Frequently Asked Questions

faq — snippet

Why does my bash for loop break on filenames with spaces?

Because you are looping over the output of a command like ls or find without -print0, and bash word-splits that output on spaces. A file named 'project notes.txt' becomes two iterations. Loop a glob directly instead — for f in *.txt — and quote the variable on use: "$f".

faq — snippet

How do I loop a fixed number of times in bash?

Use a brace range for a simple count, for i in {1..10}, or a C-style loop when you need arithmetic, for ((i=0; i<10; i++)). The brace range is fixed at parse time, so for i in {1..$n} does NOT work — use the C-style form when the bound is a variable.

faq — snippet

What is the difference between a for loop and a while loop in bash?

A for loop iterates a known list (files, a range, an array). A while loop runs until a condition changes, which is the right tool for reading a file line by line or polling until something is ready. Reading lines with a for loop word-splits the content; use while IFS= read -r line for that.

faq — snippet

How do I loop over an array in bash?

Use the quoted full-array expansion: for x in "${arr[@]}". The quotes and [@] keep each element as one item even if it contains spaces. Without quotes, "${arr[@]}" word-splits again and you lose the element boundaries.