You can issue commands to Linux directly on the command line, or you could put them in a script so that a bunch of commands can run at once. Even though you could, in theory, do away with scripts altogether and issue everything on the command line, scripts make sense for various reasons. One is that control statements like “for” and “if-else” statements are easier to input, read, and debug from within a script. The “for” statement in bash scripts is a close analog of the “foreach” statement we find in Python, PHP, and JavaScript.
Even though bash doesn’t have a native “foreach” statement, we can easily replicate its functionality using “for” statements. We’ll see how the foreach statement is used in various languages, and how we can make something similar in bash.
Why the “Foreach Statement is Special”
The first popular language to use the “foreach” statement was Perl. However, it wasn’t anything special since it was a synonym for the more common “for” statement. Languages like C had been using “for” for years, and there didn’t appear to be a need for anything else. However, starting with Perl and then perfected later by languages like PHP, the “foreach” statement has found its niche and use case.
To be clear, there’s nothing wrong with the old “for” statement. Any operation for which you use “foreach” can be performed using “for” as well. It’s just that the foreach statement is more elegant for certain types of operations.
Specifically, when it comes to iterating over the elements of a list or an array, the foreach statement is easier. It’s such a common programming operation that the need was felt to have a dedicated syntax to go over each element instead of using the “for” statement. For comparison, let’s say I have an array containing a list of fruits. In PHP, if I want to iterate over them using the “for” statement, here’s what it can look like:
$fruits = array("apple", "banana", "cherry");
$length = count($fruits);
for ($i = 0; $i < $length; $i++) {
echo $fruits[$i] . "\n";
}
Note that I have to do the following:
- Get the length of the array
- Create a for-loop to iterate over the index starting from “0” to the length of the array minus one
- Use that number to extract each element from the array index
It works fine – there’s nothing wrong with its efficiency, but it’s certainly a bit clumsy. Looking at the code, you don’t immediately see what the for-loop is trying to do. Now compare the same code in PHP using the “foreach” syntax:
$fruits = array("apple", "banana", "cherry");
foreach ($fruits as $fruit) {
echo $fruit . "\n";
}
This second syntax is much easier to read! You don’t need to get the length of the array in advance, and you don’t need to create a counter that increments the index. Reading the code is also easier, since it reads more like English and makes it clear that you’re iterating over a collection of objects. These are not minor advantages, and when performing them repeatedly, the readability and ease of writing can make a huge difference in the long term. As we see further down, bash uses the regular “for” statement when it needs to iterate over collections and arrays. But having a dedicated “foreach” statement can improve code clarity. It’s mostly a philosophical consideration.
For these reasons, the “foreach” statement is now a staple in modern programming languages.
Bash Doesn’t have a “foreach” Statement
Bash, however, doesn’t have a dedicated “foreach” statement. Instead, it uses the “for” statement just like in older languages. There is a crucial difference, however. The “for” statement is bash is flexible in ways that the “for” statements in other languages are not. This means that while we can use the “for” statement in bash traditionally by getting the length of the array and iterating over the index bit by bit, we don’t have to.
Instead, we can use the “for” statement in bash, like this:
# For loop in Bash acting like foreach
fruits=("apple" "banana" "cherry")
for fruit in "${fruits[@]}"; do
echo "$fruit"
done
The above code will print:
apple
banana
cherry
Note that despite using a “for” statement and not a “foreach” statement, we were able to iterate through the contents of the array even though we didn’t take any preliminary steps like determining the array size, or creating an index counter. The reason this works is that in bash, the “for” statement is flexible. It can be used for regular counter increments, iterating over containers, arrays, and lists like we just saw, or even over command-line outputs.
Here’s an example of iterating over a range of numbers using bash’s “for” statement:
for i in {1..5}; do
echo "Number: $i"
done
Another example of using bash to iterate over a set of files:
for file in *.txt; do
echo "Processing $file"
done
You can see how easy it is. Given that bash’s for statement is so flexible, there’s no need for a separate “foreach” statement.
Alternatives to the “foreach” Statement in Bash
As we’ve seen, in most cases we can just use the “for” statement as a substitute for “foreach” that we find in other languages. However, there are some situations where we need to perform more complicated actions and must turn to other control statements.
For example, let’s say I have a file and I want to print each line of that file, prefixed with something like “Line: “. Doing this using a for loop might look something like this:
for line in $(cat file.txt); do
echo "Line: $line"
done
This looks like it could work, but unfortunately, it doesn’t. The problem is that the statement splits content using whitespaces instead of newline characters. So the output of the following statement looks like this:
As you can see, the file consists of a file line with a date, and then two other lines. Instead of getting three lines in the output, however, we got each word in a separate line. This is to be expected if our input splits using a whitespace.
However, we can accomplish the same thing using a while statement:
while read line; do
echo "Line: '$line'"
done < file.txt
Here’s the output:
As you can see, the while statement using “read”, splits everything based on lines instead of whitespaces. We can solve this with a for loop by setting something called the “IFS” variable. But even then it’s not ideal because it it reads the entire file into memory at once, unlike the “read” statement which takes the lines one by one which can greatly improve performance when file sizes are large.
Conclusion
While bash doesn’t have a dedicated “foreach” statement, the simpler “for” statement is more than up to the task because it’s more flexible than the “for” statements found in other languages like Perl and even PHP. In bash, you can use the “for” statement to iterate through not just counters, but lists, containers, files, and even words inside a file, rendering the “foreach” statement redundant. While you might still need other control statements like “while” for more niche and complicated operations, the for statement is powerful enough to use most of the time.
I’m a NameHero team member, and an expert on WordPress and web hosting. I’ve been in this industry since 2008. I’ve also developed apps on Android and have written extensive tutorials on managing Linux servers. You can contact me on my website WP-Tweaks.com!
Leave a Reply