The POSIX Shell Command Language allows redirections to follow compound commands. The standard says (emphasis mine)
2.9.4 Compound Commands
The shell has several programming constructs that are "compound commands", which provide control flow for commands. Each of these compound commands has a reserved word or control operator at the beginning, and a corresponding terminator reserved word or operator at the end. In addition, each can be followed by redirections on the same line as the terminator. Each redirection shall apply to all the commands within the compound command that do not explicitly override that redirection.
For aesthetic reasons I want to put a heredoc before my while loop as in
<<'RECORDS' while
foo:bar
baz:quux
...
RECORDS
IFS=: read -r A B
do
# do something with A and B
done
because it makes the code easier to follow. However, it doesn't work in the shells I tried it (bash
and dash
). I get errors saying that the "while" command was not found and I assume that means that after the leading heredoc a simple command is expected and not a compound command.
I cannot move the heredoc to after read
because then it reads the first line from a new heredoc on every iteration. I know that I can fix this by moving the heredoc to after done
. I could also open a fd to a heredoc with exec
before the loop and add a redirection to read
.
What's the reason redirections cannot occur before a compound command? Is there a shell that supports it since it's not explicitly prohibited by POSIX?
The Z shell (zsh) will accept this non-standard syntax. POSIX standardizes existing practice, and in the case of the shell, the reference implementation was the Korn shell (ksh88, or just ksh). Since that implementation supported only redirections following the compound statement that was what was standardized.
There are several ways you can write your loop in a more portable (and much easier to read) form. For example:
while
IFS=: read -r A B
do
echo $A $B
done <<'RECORDS'
foo:bar
baz:quux
RECORDS
is the commonest way I would do something like this. Or you could wrap the loop up in a function and redirect the input to the function:
loop()
{
while
IFS=: read -r A B
do
echo $A $B
done
}
<<'RECORDS' loop
foo:bar
baz:quux
RECORDS
This allows you to mix up the call to the function in amid the data as you originally wanted (I still don't really understand why you think this is clearer:-) )
Both these techniques work with bash
, dash
, ksh
, ksh93
, yash
and zsh
.