2

How to pipe stderr, and not stdout? perfecly captures my problem, and the first answer is exactly how I initially tried to solve it:

(echo stdout; echo 1>&2 stderr) 2>&1 >/dev/null | less

(The subshell command with echos is a minimal placeholder thanks to user1934428 to demonstrate the issue; my actual problem has a more useful command, but one that everyone else can't run.)

However, it doesn't work: It's showing both stdout and stderr together. If I remove the pipe, it works as expected, only showing stderr. Eventually I realized this might be a shell thing, and tried bash: It worked perfectly there. I'm using Zsh: what about Zsh makes this fail?

D0SBoots
  • 705
  • 6
  • 18
  • If you don't show a *specific* command that people other than you can run without changes to see the problem, how do we know that the problem is really with the parts that are shown, vs the *real* `command` (which, aside, isn't a good name to use as a standin, because there's actually a shell-builtin by that name) being used in your real-world use case? – Charles Duffy Sep 20 '19 at 14:39
  • See also the [mre] guidelines in the Help Center; the code shown needs to *actually reproduce the problem*. – Charles Duffy Sep 20 '19 at 14:40
  • You are, of course, correct. The problem is that the specific command is not something I can publically share, or that anyone else can run. I had hoped that this was a case of "this is a semi-well known quirk in Zsh," as opposed to, "No, this really ought to work, what's the exact command you're running." – D0SBoots Sep 23 '19 at 19:35
  • That said, thanks to the first answer I now have a minimal version that repros for me. – D0SBoots Sep 23 '19 at 19:43
  • 1
    Interesting. Notably. it *doesn't* happen if you add another layer of subshells: `( (echo stdout; echo 1>&2 stderr) 2>&1 >/dev/null) | less`. My focus is on bash rather than zsh, so I'll let someone else step in as to the "why". – Charles Duffy Sep 23 '19 at 20:01
  • I encountered the same problem, and just fyi there is a short & sweet alternative command to reproduce this behavior `echo input | dd 2>&1 > /dev/null | cat` - which always puts "input" somewhere in between the stderr lines for me in zsh, but not in bash – xeruf May 17 '20 at 09:48

2 Answers2

5

This is due to MULTIOS, which duplicates the output streams when you supply multiple redirections. Pipes are implicit redirections.

Type ls >/dev/null | less in your zsh and you will see output even though you redirected it. That's multios duplicating the stream.

unsetopt multios will provide the desired behaviour. This is quite a handy feature if I wouldn't always forget about it until it irritates me.

http://zsh.sourceforge.net/Doc/Release/Redirection.html#Multios

WolleTD
  • 372
  • 1
  • 7
1

Both behave the same for me, in that they only show stderr.

For testing, I created a command which outputs to stdout and stderr, and works the same in Zsh and bash:

(echo stdout; echo 1>&2 stderr)

prints

stdout
stderr

Now I do your redirection:

 (echo stdout; echo 1>&2 stderr) 2>&1 >/dev/null

Here, I see on bash and zsh

stderr

but of course written to standard output, because 2>&1 first redirects the stderr of the command to what is at the moment standard output, and then discards the standard output of the command.

user1934428
  • 19,864
  • 7
  • 42
  • 87
  • Excellent! Your example reproduces the issue for me, in a form that other people can try. Specifically: (echo stdout; echo 1>&2 stderr) 2>&1 >/dev/null Shows only "stderr", while (echo stdout; echo 1>&2 stderr) 2>&1 >/dev/null |less shows stderr *and* stdout. – D0SBoots Sep 23 '19 at 19:37