Bash for Loop: From Repetitive Tasks to Looping Magic
This article explains how the Bash for loop works, what you can do with it, and how to write your own loops to automate repetitive tasks in the Linux or macOS terminal. You will learn the syntax, see concrete examples, and understand common pitfalls so you can use loops confidently in scripts and one-liners.
What Is a Bash for Loop?
A for loop in Bash is a control structure that repeats a block of commands once for each item in a list, range, or set of files. Instead of typing the same command ten times, you write one loop that runs it ten times with a different value each time. The loop is a core part of shell scripting and is used for everything from renaming batches of files to processing output from other commands.
Basic Syntax and How It Works
The most common form of the Bash for loop iterates over a list of words:
for variable in list
do
commands
done
You can also write it on one line with semicolons:
for i in 1 2 3; do echo "$i"; done
Here, i is the loop variable. On the first iteration, i takes the value 1; on the second, 2; and so on. The commands between do and done run each time. The list can be explicit words, a range like {1..5}, a command substitution like $(ls *.txt), or an array. The loop ends when there are no more items.
Three Common Ways to Write a for Loop
Bash offers several syntaxes that look similar but behave differently. Knowing which one to use matters for portability and avoiding errors.
- C-style loop:
for ((i=0; i<10; i++))— useful when you need an index or a counter with arithmetic conditions. It is a Bash extension, not POSIX. - List loop:
for i in a b c— the classic POSIX-compatible form. It works inshas well as Bash. - Range loop:
for i in {1..10}— a Bash brace expansion that generates the list. It does not work in oldersh.
For example, to print numbers 1 to 5:
for i in {1..5}; do echo $i; done
To loop over files in the current directory:
for file in *.txt; do echo "Found: $file"; done
If no .txt files exist, the loop still runs once with the literal string *.txt. A common guard is to check if the file exists inside the loop.
Real-World Examples: Renaming and Processing Files
One of the most frequent uses of a for loop is batch-renaming files. For instance, to add a prefix backup_ to all .jpg files:
for f in *.jpg; do mv "$f" "backup_$f"; done
Another example is converting all .txt files to uppercase content using tr:
for f in *.txt; do tr 'a-z' 'A-Z' < "$f" > "${f%.txt}.upper"; done
The expression ${f%.txt} removes the trailing .txt so you can build a new name. Loops also work with command output. To list the size of every .log file:
for f in $(ls *.log); do wc -c "$f"; done
However, using ls inside a command substitution can break if filenames contain spaces. A safer approach is to use the glob directly: for f in *.log. This is a common mistake that leads to unexpected behavior.
Common Pitfalls and How to Avoid Them
Several issues trip up beginners and even experienced scripters. The most frequent are:
- Forgetting to quote variables:
for f in $filessplits on spaces, so filenames with spaces get broken. Always use"$f"inside the loop body. - Using
lsin a loop:for f in $(ls)is fragile. Use globs like*.txtinstead. - Infinite loops: In a C-style loop, if the condition never becomes false, the loop runs forever. Check your increment and condition.
- Word splitting on command substitution:
for i in $(some_command)splits on spaces, tabs, and newlines. If you need each line as one item, use awhile readloop instead. - Brace expansion with variables:
{1..$n}does not work because brace expansion happens before variable expansion. Useseq 1 $nor a C-style loop instead.
For example, to loop over lines in a file safely, you would use:
while IFS= read -r line; do echo "$line"; done < file.txt
This is not a for loop, but it is the correct tool when each line is a separate item, especially if lines contain spaces.
Performance and Efficiency Tips
Bash loops are not the fastest way to process large amounts of data. For heavy text processing, tools like awk, sed, or xargs are often faster. But for most administrative tasks, a for loop is perfectly fine. Some tips:
- Avoid calling external commands inside the loop when possible. For example,
echois a builtin, butwcandtrare external. If you need to process many files, consider usingfind -execorxargs. - Use
[[ ... ]]instead of[ ... ]for tests in Bash because it is a builtin and more robust. - If you need to accumulate results, assign to a variable inside the loop and use it after the loop. For example, to sum numbers:
sum=0
for n in {1..10}; do
sum=$((sum + n))
done
echo $sum
This prints 55. The arithmetic expansion $(( ... )) is a Bash feature that works with integers.
Alternatives to for Loops
Sometimes a for loop is not the best choice. Here are common alternatives and when to use them:
- while loop: Use when you do not know how many iterations you need, or when reading input line by line.
- find -exec: For running a command on many files, especially with complex conditions. It is often faster and handles odd filenames safely.
- xargs: Converts input lines into command arguments. Good for parallel processing with
-P. - awk: For text processing that involves columns and conditions. It can replace many loops over file content.
- mapfile: Reads lines into an array, then you can loop over the array with a
forloop if needed.
For example, to delete all .tmp files, you could use find . -name "*.tmp" -delete instead of writing a loop. Knowing these alternatives makes your scripts cleaner and more efficient.
Frequently Asked Questions
How do I loop over a range of numbers with a step?
Bash brace expansion does not support a step directly. Use seq: for i in $(seq 1 2 10); do echo $i; done prints 1, 3, 5, 7, 9. Or use a C-style loop: for ((i=1; i<=10; i+=2)).
Why does my loop not work with filenames that have spaces?
Because unquoted variables and command substitutions split on spaces. Quote all variables ("$f") and avoid $(ls). Use globs directly: for f in *.txt handles spaces correctly because the glob expands to separate words for each file.
What is the difference between for i in {1..10} and for ((i=1; i<=10; i++))?
The first uses brace expansion to create a list of 10 items, then iterates over them. The second is a C-style arithmetic loop that evaluates the condition each time. The brace version is simpler, but the C-style version allows more complex conditions and increments.
Can I use a for loop to iterate over lines of a file?
Yes, but it is not recommended because for splits on whitespace by default. Use while IFS= read -r line; do ...; done < file to preserve spaces and special characters in each line.
How do I break out of a for loop early?
Use the break command. For example, to stop when you find a file named stop.txt: for f in *; do if [[ $f == stop.txt ]]; then break; fi; done. The continue command skips the rest of the current iteration and moves to the next item.
Why does my loop run once even when there are no matching files?
If the glob does not match anything, Bash leaves the pattern as a literal string. The loop then runs once with that string. To avoid this, check if the file exists inside the loop: [[ -e $f ]] || continue.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.