Commands in a pipeline run concurrently, that's the whole point of pipes, and inter-process communication mechanism.
In:
cmd1 | cmd2
cmd1
and cmd2
are started at the same time, cmd2
processes the data that cmd1
writes as it comes.
If you wanted cmd2
to be started only if cmd1
had failed, you'd have to start cmd2
after cmd1
has finished and reported its exit status, so you couldn't use a pipe, you'd have to use a temporary file that holds all the data the cmd1
has produced:
cmd1 > file || cmd2 < file; rm -f file
Or store in memory like in your example but that has a number of other issues (like $(...)
removing all trailing newline characters, and most shells can't cope with NUL bytes in there, not to mention the scaling issues for large outputs).
On Linux and with shells like zsh
or bash
that store here-documents and here-strings in temporary files, you could do:
{ cmd1 > /dev/fd/3 || cmd2 <&3 3<&-; } 3<<< ignored
To let the shell deal with the temp file creation and clean-up.
bash version 5 now removes write permissions to the temp file after creating it, so the above wouldn't work, you'll need to work around it by restoring the write permission first:
{ chmod u+w /dev/fd/3
cmd1 > /dev/fd/3 || cmd2 <&3 3<&-; } 3<<< ignored
Manually, POSIXly:
tmpfile=$(
echo 'mkstemp(template)' |
m4 -D template="${TMPDIR:-/tmp}/XXXXXX"
) && [ -n "$tmpfile" ] && (
rm -f -- "$tmpfile" || exit
cmd1 >&3 3>&- 4<&- ||
cmd2 <&4 4<&- 3>&-) 3> "$tmpfile" 4< "$tmpfile"
Some systems have a non-standard mktemp
command (though with an interface that varies between systems) that makes the tempfile creation a bit easier (tmpfile=$(mktemp)
should be enough with most implementation, though some would not create the file so you may need to adjust the umask
). The [ -n "$tmpfile" ]
should not be necessary with compliant m4
implementations, but GNU m4
at least is not compliant in that it doesn't return a non-zero exit status when the mkstemp()
call fails.
Also note that there's nothing stopping you running any code in the console. Your "script" can be entered just the same at the prompt of an interactive shell (except for the return
part that assumes the code is in a function), though you can simplify it to:
output=$(cmd) || grep foo <<< "$output"
cmd1 > file || cmd2 < file
andoutput=$(cmd) || grep foo <<< "$output"
achieve the results in full. With the former syntax I have to later clean up by removing the file, but this is not a big deal and provides quite a few options. Thanks. – I'll Eat My Hat Oct 25 '18 at 19:37(f=\
mktemp`; exec 3>$f 4<$f; rm $f; cmd1 >&3 || cmd2 <&4)` if you don't want to have to clean up afterwards. – Oct 26 '18 at 00:46