Bash For Loop: The Complete Guide With Real Sysadmin Examples

A bash for loop repeats a block of commands once for every item in a list. That list can be a set of words, a numeric range, a glob of filenames, the lines of a file, or the elements of an array. If you administer Linux servers, the for loop is the single construct that turns a tedious manual chore into one line you can read, audit, and rerun. Rename two hundred files, ping a rack of hosts, back up a dozen directories — each becomes a few characters of shell.

This guide covers every form of the for loop in bash shell, with heavy code examples drawn from real administrative work, plus the one quoting habit that separates scripts that survive contact with messy filesystems from scripts that silently corrupt them.

Key Takeaways
• A bash for loop iterates commands over a list of items: words, numbers, files, or array elements.
• There are two syntaxes: the list form (`for x in a b c; do … done`) and the C-style form (`for ((i=0; i• Brace expansion (`{1..10}`) and `seq` generate numeric ranges; steps are supported (`{0..20..2}`).
• The most common sysadmin use is looping over files with a glob (`for f in *.txt`).
Always quote your loop variable (`”$f”`) and loop over globs, never over `$(ls)`.

What does a bash for loop actually do?

At its core, a for loop binds a variable to each item in a list, then runs the loop body once per binding. The structure is `for`, a variable name, the keyword `in`, the list, and a `do … done` body.

“`bash for fruit in apple banana cherry; do echo “Processing $fruit” done “`

Output:

“` Processing apple Processing banana Processing cherry “`

The loop variable (`fruit` here) is just a normal shell variable. On each pass it holds the current item, and you reference it as `$fruit` inside the body. The list is whitespace-separated, which is both convenient and — as we will see — the source of the most common bug in shell scripting.

What are the two for loop syntaxes in bash?

Bash supports two distinct for loop forms. The list form iterates over a sequence of items. The C-style form uses an arithmetic counter, matching the `for` you know from C, Java, or JavaScript.

The list form is what you reach for ninety percent of the time:

“`bash

for service in nginx mysql redis; do systemctl restart “$service” done “`

The C-style form is right when you need an index, a custom step, or a condition that is not simply “exhaust this list”:

“`bash

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

Note the double parentheses and the C-like three-part header: initialization, condition, increment. This form is bash-specific (it works in `bash`, not in strict POSIX `sh`), so keep it for scripts with a `#!/bin/bash` shebang. For a deeper look at choosing the right shell and writing portable scripts, see our guide on shell scripting fundamentals.

How do you loop over a range of numbers?

For numeric ranges, brace expansion is the cleanest approach. The syntax `{start..end}` expands to every integer in the range before the loop even runs.

“`bash

for i in {1..10}; do echo “Count: $i” done “`

You can add a third value for a step, producing every Nth number:

“`bash

for i in {0..20..2}; do echo “Even: $i” done “`

Brace expansion also works with letters and zero-padding, which is handy for generating predictable names:

“`bash

for month in {01..12}; do echo “Archiving month $month” done “`

There is one limitation worth knowing: brace expansion does not accept variables. `{1..$n}` will not expand, because brace expansion happens before variable substitution. When the bounds are dynamic, reach for `seq` or the C-style form instead.

“`bash

end=15 for i in $(seq 1 “$end”); do echo “Dynamic count: $i” done

for i in $(seq 0 5 50); do echo “Stepping: $i” done “`

Loop form Syntax Best use case
List form `for x in a b c; do` Iterating over known items or words
Glob `for f in *.log; do` Processing files in a directory
Brace range `for i in {1..10}; do` Fixed numeric range, optional step
C-style `for ((i=0;i Index-driven loops, custom conditions
seq `for i in $(seq 1 “$n”); do` Range bounds that come from variables
Array `for x in “${arr[@]}”; do` Iterating over array elements

How do you loop through files in bash?

This is the workhorse pattern for anyone managing a server: bash loop through files using a glob. The shell expands the glob into matching filenames, and the loop processes each one.

“`bash

for f in *.txt; do echo “Found file: $f” done “`

The `*.txt` is a glob, expanded by the shell into the list of matching files. This is the correct, robust way to iterate over files — and the reason is worth dwelling on later. First, some real examples.

Bulk rename — append a `.bak` suffix to every config file:

“`bash for f in *.conf; do cp — “$f” “${f}.bak” done “`

Convert or process in bulk — recompress every `.log` file with gzip:

“`bash for f in /var/log/myapp/*.log; do gzip “$f” done “`

Rename with substitution — change every `.jpeg` extension to `.jpg`:

“`bash for f in *.jpeg; do mv — “$f” “${f%.jpeg}.jpg” done “`

The `${f%.jpeg}` is parameter expansion that strips the matching suffix — a clean way to derive the new name from the old one. If a glob matches no files, bash by default leaves the literal pattern unexpanded (so `f` becomes the string `*.jpeg`). Setting `shopt -s nullglob` makes a non-matching glob expand to nothing, which is usually what you want in scripts.

For a refresher on the file-manipulation commands you will pair with these loops, see our reference on essential Linux commands.

How do you loop over command output and arrays?

Sometimes the list does not come from a glob but from another command — a file of hostnames, the output of a query, or an array you built earlier.

Looping over the lines of a file is common for things like a host list or a user list. The textbook one-liner uses command substitution:

“`bash

for user in $(cat users.txt); do echo “Creating $user” done “`

This is fine when you control the file and know it contains simple, space-free tokens. But for robust line-by-line reading, `while read` is the correct tool — see our note on its tradeoffs below.

Arrays give you a first-class list that survives spaces correctly when quoted properly:

“`bash

servers=(“web01” “db primary” “cache-02”) for s in “${servers[@]}”; do echo “Connecting to $s” done “`

The `”${servers[@]}”` syntax — quoted, with `[@]` — expands each element as a separate word, preserving spaces inside elements. Drop the quotes and `db primary` splits into two iterations. This same principle drives the central gotcha of the whole topic.

What is the most common bash for loop bug?

Here is the insight that will save you a 2 a.m. incident review. The most common bash for-loop bug is not a syntax error. It is looping over command output like `for f in $(ls)` or over an unquoted variable, both of which silently break on filenames containing spaces.

Consider a directory with a file literally named `my report.txt`. This loop does the wrong thing:

“`bash

for f in $(ls *.txt); do rm “$f” # tries to rm “my” and “report.txt” separately done “`

Because `$(ls …)` produces text, the shell applies word splitting to it. The filename `my report.txt` becomes two items, `my` and `report.txt`, and your `rm` targets two files that do not exist while missing the one that does. No error is thrown. The loop just quietly operates on the wrong things.

The robust pattern is to loop over a glob directly and always quote the variable:

“`bash

for f in *.txt; do rm — “$f” # handles “my report.txt” correctly done “`

The shell’s glob expansion produces a true list of filenames — each match is one item, regardless of spaces, tabs, or special characters. Quoting `”$f”` then ensures the value is passed as a single argument. This one habit — glob plus quotes, never parse `ls` — eliminates an entire class of bugs that bites every sysadmin exactly once, on a file named something like `my report.txt`, usually in production. Internalize it now and you skip that rite of passage.

The same quoting discipline applies everywhere `$f` appears: `cp “$f” “$dest”`, `[ -f “$f” ]`, `du -sh “$f”`. When in doubt, quote.

What are some real-world sysadmin for loop examples?

These are the patterns you will actually type into a terminal.

Ping a list of hosts and report which are up:

“`bash for host in 10.0.0.1 10.0.0.2 10.0.0.3; do if ping -c1 -W1 “$host” &>/dev/null; then echo “$host is UP” else echo “$host is DOWN” fi done “`

Back up multiple directories into timestamped tarballs:

“`bash stamp=$(date +%Y%m%d) for dir in /etc /home /var/www; do name=$(basename “$dir”) tar czf “/backups/${name}-${stamp}.tar.gz” “$dir” done “`

Batch-create users from an array with a shared default:

“`bash for u in alice bob carol; do useradd -m -s /bin/bash “$u” echo “Created $u” done “`

These backup and provisioning routines are exactly the kind of thing you schedule to run unattended. Pair a for loop with a scheduled job and your nightly maintenance writes itself.

How do you use break and continue in a for loop?

`break` exits the loop entirely; `continue` skips to the next iteration. Both are essential for control flow inside a loop body.

“`bash for f in *.log; do

if [ ! -s “$f” ]; then continue fi

if [ “$(stat -c%s “$f”)” -gt 104857600 ]; then echo “Large file found: $f” break fi echo “Processing $f” done “`

`continue` here jumps past empty files without processing them; `break` stops the whole loop the moment a condition is met. Use them to keep loop bodies flat and readable instead of nesting deep conditionals.

When should you use a one-liner versus a script?

A for loop works identically on the command line and in a script — the only difference is how you separate the parts. Interactively, semicolons stand in for newlines:

“`bash for f in *.txt; do echo “$f”; done “`

The semicolons before `do` and before `done` are mandatory in the one-liner form; they replace the line breaks you would otherwise use. For anything you will run more than once, or anything longer than a single short body, move it into a script file where the multi-line form is far more readable and reviewable.

Use a one-liner for quick, throwaway operations you are running once. Promote it to a script the moment you want to save it, reuse it, schedule it, or hand it to a colleague. A loop that lives in a file under version control is a loop you can audit and trust.

If your task is fundamentally “keep going until a condition changes” rather than “process this list,” a `while` loop is often the better fit — particularly for reading a file line by line safely.


Run your for loops on a real Linux server with DarazHost. DarazHost VPS and dedicated servers give you full root shell access — the genuine Bash environment to write and run for loops and automation scripts across your files, backups, and admin tasks. Pair them with cron to schedule your batch jobs, and lean on 24/7 support when you need it. It is a real Linux server, ready to automate.


For the bigger picture of how looping, scripting, and scheduling fit together into a managed server workflow, read our complete guide to Linux server administration.

Frequently asked questions

What is the difference between the list form and the C-style for loop in bash?

The list form (`for x in a b c; do`) iterates over a sequence of items and is ideal for files, words, and ranges. The C-style form (`for ((i=0; i

How do I loop through all files in a directory in bash?

Use a glob: `for f in *; do echo “$f”; done`. To match a specific type, use `for f in *.txt`. Always quote the loop variable as `”$f”` so filenames containing spaces are handled correctly, and avoid `for f in $(ls)`, which breaks on spaces.

How do I create a numeric range in a bash for loop?

Use brace expansion for fixed ranges: `for i in {1..10}`. Add a step with `{0..20..2}`. When the bounds come from variables, brace expansion will not expand them — use `seq` instead: `for i in $(seq 1 “$end”)`, or use the C-style form.

Why does my for loop break on filenames with spaces?

Because you are likely looping over `$(ls)` or an unquoted variable, and the shell applies word splitting to the text, turning `my report.txt` into two items. Loop over a glob directly (`for f in *.txt`) and quote the variable (`”$f”`). Glob expansion preserves whole filenames; parsing `ls` does not.

Can I use a for loop as a one-liner?

Yes. Replace the newlines with semicolons: `for f in *.txt; do echo “$f”; done`. The semicolons before `do` and `done` are required. Use one-liners for quick interactive tasks; move anything reusable into a script file for readability and version control.

About the Author

Leave a Reply