Reputation: 51
Bash has two features that both work, but not together.
|& is an alternate syntax for piping both stdout and stderr. Example:
cmd |& tee file
is equivalent to
cmd 2>&1 | tee file
! history expressions allows word selection such as !*
for all but word 0 (and it can also be written !:*
). Example:
echo one two
echo zero !*
The second echo produces
echo zero one two
So far, so good. But when combined, bash does the wrong thing.
echo one |& tee /tmp/file
one
echo zero !*
echo zero one | & tee /tmp/file
bash: syntax error near unexpected token `&'
(The line "one" and the line starting with "bash:" are output and the line
echo zero one | & tee /tmp/file
is history reiterating the expansion it generated and appears because I have the histexpand
option (same as set -H
) enabled.)
Note that the history expansion inserted a space between | and &. That's not helpful, and in my opinion, it is not correct. I wanted the |&
digraph left together, so the expansion should have been
echo zero one |& tee /tmp/file
(History expansion doesn't alter the |&
sequence unless history word selection is used.)
Is this a known bash bug? Is there a workaround, other than
Is there a bash version with a fix for this? How/where should I submit this bug?
(I mostly use GNU bash, version 4.4.20(1)-release (x86_64-redhat-linux-gnu) on Alma 8.9.)
Upvotes: 1
Views: 84
Reputation: 683
Yes it's a well known problem that the main parser and the history expansion mechanism don't agree on what constitutes a token.
You can use the bashbug
utility that's part of the Bash suite to submit a bug report, but since it's already known, I suggest joining the [email protected]
mailing list for further discussion & feedback.
In the meantime I strongly recommend shopt -s histverify histreedit
to make sure that the expansion generated by !
is what you intend before trying to run them, and to allow re-editing if the expansion fails.
Personally I avoid the |&
except for quick hacks, because of its very weird split personality.
One might reasonably expect that any "pipe operator" would be set up before the redirections in the components on either side of it, but in the case of the |&
operator, it first sets up stdout of the left side, then does all the internal redirections on the left side and only then does it do the equivalent of 2>&1
.
So if you write:
{ foo >&3 |& bar ; } 3>> /some/file
then both stdout and stderr from foo
winds up in /some/file
, and stdin for bar
is empty.
If anyone thinks this is "obvious" or "preferred", please feel free to leave a comment, but I think it's pretty insane.
Upvotes: 3