Skip to the content.

Loop over file names without using ls

This is a common misconception coming from old bash versions or old shells altogether. When executing loops over file names, the for loop has direct support for path expansion which means you don’t have to use ls in a subshell to obtain the same result.

The form using a subshell

This form is very commonly used, even though it uses a subshell and it’s less visually appealing – obviously, this is personal opinion.

for FILE in `ls /etc/*rel*`; do ... $FILE ... ; done

Spawning a subshell that executes ls can be expensive and most probably the reason why this form is so common is because there is little knowledge of the bash-only alternative.

Bash-only for loop over file names

In the vast majority of the times you need to use ls, the following form works just as well:

for FILE in /etc/*rel*; do ... $FILE ... ; done

Whatever wildcard filter you’re passing to ls, works with bash too. Mostly because before it’s passed to ls, bash expands /etc/*rel* to the actual filenames that match it.

This form of looping over file names uses the bash feature called pathname/filename expansion. I also use it very often with arrays, such as:

ARR=( /etc/*rel* )

and then further process the ${ARR[*]} contents.

The nullglob case

By default, the nullglob feature is not enabled in bash. This causes the unmatched patterns to be sent as-is as input to the bash constructs. To avoid this, set nullglob in advance, usually at the beginning of your script:

shopt -s nullglob

So whenever a pattern doesn’t match any existing file, it will be expanded to null, which means a for loop won’t execute at all.

Conclusion

I’m done judging which form is better. In many cases, not having to spawn a new subshell can translate to more stability when the machine is under load. In some other cases, it looks clearer without the backquotes and the ls.

However, if you were using the ls form before, just know that there is a bash-only alternative to executing a loop over file names.