do_something | ( while read -r v; do
. . .
done )
I’ve found that it has fewer edge cases (except it creates a subshell, which can be avoided in some shells by using braces instead of parens) do_something | ( while read -r v; do
. . .
done )
I’ve found that it has fewer edge cases (except it creates a subshell, which can be avoided in some shells by using braces instead of parens)1. You don't need the parentheses.
2. If you use process substitution [1] instead of a pipe, you will stay in the same process and can modify variables of the enclosing scope:
i=0
while read -r v; do
...
i=$(( i + 1))
done < <(do_something)
The drawback is that this way `do_something` has to come after `done`, but that's bash for you ¯\_(ツ)_/¯[1] https://www.gnu.org/software/bash/manual/html_node/Process-S...
I think the redirection can come first, though (not at a computer to test):
< <( do_something ) while read . . . $ < <( echo foo ) while read -r f; do echo "$f"; done
-bash: syntax error near unexpected token `do'
$ < <( echo foo ) xargs echo
foo
$ bash --version
GNU bash, version 5.1.4(1)-release (x86_64-apple-darwin20.2.0)For documentation purposes, this is the exact thing I tried to run:
$ < <(echo hi) while read a; do echo "got $a"; done
-bash: syntax error near unexpected token `do'
$ while read a; do echo "got $a"; done < <(echo hi)
got hi
Maybe there is another way... $ zsh -c '< <(echo hi) while read a; do echo "got $a"; done'
got hi
My position isn't that it is a good reason to switch shells, but if you're using it anyway then it is an option.Bash uses /dev/fd when available, but also appears to have an internal implementation which silently creates named pipes and cleans them up. In Bash 5.0.18 on AIX, fake process substitution works just fine, in my testing.
One common pattern I use this for is running a bunch of checks/tests, e.g.
EXIT_CODE=0
while read -r F
do
do_check "$F" || EXIT_CODE=1
done < <(find ./tests -type f)
exit "$EXIT_CODE"
This is a more complicated alternative to the following: find ./tests -type f | while read -r F
do
do_check "$F" || exit 1
done
The simpler version will abort on the first error, whilst the first version will always run all of the checks (exiting with an error afterwards, if any of them failed)>Different shells exhibit different behaviors in this situation:
>- BourneShell creates a subshell when the input or output of anything (loops, case etc..) but a simple command is redirected, either by using a pipeline or by a redirection operator ('<', '>').
>- BASH, Yash and PDKsh-derived shells create a new process only if the loop is part of a pipeline.
>- KornShell and Zsh creates it only if the loop is part of a pipeline, but not if the loop is the last part of it. The read example above actually works in ksh88, ksh93, zsh! (but not MKsh or other PDKsh-derived shells)
>- POSIX specifies the bash behaviour, but as an extension allows any or all of the parts of the pipeline to run without a subshell (thus permitting the KornShell behaviour, as well).
paste -d \\n <(do_something1) <(do_something2) | while read -r var1 && read -r var2; do
... # var1 comes from do_something1, var2 comes from do_something2
done