31

According to "Linux: The Complete Reference 6th Edition" (pg. 44), you can pipe only STDERR using the |& redirection symbols.

I've written a pretty simple script to test this:

#!/bin/bash
echo "Normal Text."
echo "Error Text." >&2

I run this script like this:

./script.sh |& sed 's:^:\t:'

Presumably, only the lines printed to STDERR will be indented. However, it doesn't actually work like this, as I see:

    Normal Text.
    Error Text. 

What am I doing wrong here?

Naftuli Kay
  • 39,676

3 Answers3

35

I don't know what text your book uses, but the bash manual is clear (if you're a little familiar with redirections already):

If |& is used, command1’s standard error, in addition to its standard output, is connected to command2’s standard input through the pipe; it is shorthand for 2>&1 |. This implicit redirection of the standard error to the standard output is performed after any redirections specified by the command.

So if you don't want to mix standard output and standard error, you'll have to redirect standard output somewhere else. See How to grep standard error stream (stderr)?

{ ./script.sh 2>&1 >&3 | sed 's:^:\t:'; } 3>&1

Both fd 1 and 3 of script.sh and sed will point to the original stdout destination however. If you want to be a good citizen, you can close those fd 3 which those commands don't need:

{ ./script.sh 2>&1 >&3 3>&- | sed 's:^:\t:' 3>&-; } 3>&1

bash and ksh93 can condense the >&3 3>&- to >&3- (fd move).

  • The bash manual isn't entirely clear to me. I don't quite understand why smth like ./script.sh > /tmp/stdout_goes_here |& grep 'grepping_script_stderr' doesn't work as intended, ie: redirect script.sh's stdout (which, according to the manual snippet should happen first), then allow grep to process the script's stderr. Instead, stderrand tdoutboth end up instdout_goes_here` – sxc731 Nov 11 '19 at 13:05
  • 2
    @sxc731 |& is shorthard for 2>&1 |. So >/tmp/stdout_goes_here |& redirects stdout to /tmp/stdout_goes_here, then 2>&1 redirects stderr to wherever stdout is going, which is /tmp/stdout_goes_here, and finally | doesn't receive any input because the output of the command has been redirected. Keep in mind that >&1 redirects to wherever file descriptor 1 is currently going, not to wherever file descriptor 1 will end up going. To pipe stderr only and redirect stdout to a file, one way is 2>&1 >/tmp/stdout_goes_here |. – Gilles 'SO- stop being evil' Nov 11 '19 at 23:29
8

|& pipes stderr to stdin, like 2>&1 |, so the next program will get both on stdin.

$cat test.sh
#!/bin/bash
echo "Normal Text."
echo "Error Text." >&2
$./test.sh | sed 's:^:\t:'
Error Text.
        Normal Text.
$ ./test.sh |& sed 's:^:\t:'
        Normal Text.
        Error Text.
Lesmana
  • 27,439
Kevin
  • 40,767
3

|& in bash is just a (not terribly portable) shortcut for 2>&1 |, so you should see every line indented.

Lesmana
  • 27,439
jw013
  • 51,212