bash pipes

I've been using UNIX for a very long time, and bash has been my shell for almost all that time, but for the life of me I can never remember how to pipe standard error anywhere. I think my problem is I've never found any logic by which the syntax makes sense. Anyway, it's:

command 2>&1 | less

But if you want to send the standard error and output to a file, it's the other way around:

command > file 2>&1


Variants that don't work:

command | less 2>&1
command 2>&1 > file
command | 2>&1 less
command > 2>&1 file

Using &> instead of >& not only doesn't work, it generates junk files named things like "1" (or clobbers those files if they exist already).

Isn't the UNIX shell a brilliant productivity tool?

8 comments:

Anonymous said...

The way to remember this is that you're still sending a stream somewhere using ">" the number in front of the > tells it which stream to send (1 = stdout, 2 = stderr), and after the > the & says "the same place as" and then a number

so you pronounce

2>&1

"stream two goes to the same place as stream one"

Anne M. Archibald said...

Sure, that part's no problem, and in fact I usually remember that part of it. But why the >& has to be before the pipe but after the file redirection I don't understand.

Popup said...

I don't use bash, but instead try to use every opportunity to mention the Z-shell. It's basically posix compatible (i.e. pretty close to bash), but with numerous extensions, especially when it comes to tab-completion.

Give it a try! type 'gcc -' and hit [tab]
(On my system that lists all 285 possible parameters, with a short explanation for most of them.)

And, it also features (slightly) saner redirection, wherein you can do:

command |& less (in order to send stdout to a pipe)
or
command 2>&1 > file (to send stdout and stdin to a file)

Adam Sampson said...

First the shell splits the pipeline into the commands it has to run, then sets up the pipes between them, then it processes the redirections for each command in order. So if you've said:

foo 2>&1 | bar

the two commands it's got to run are "foo 2>&1" and "bar". First it sets up the pipe between the two (so foo's FD 1 is the pipe), then it does the redirections (so foo's FD 2 gets connected to FD 1, the pipe).

The reason you need to put the >& after the file redirection is that the redirections are processed in order, left to right. So if you say:

foo >file 2>&1

then first it connects FD 1 to "file", then connects FD 2 to whatever FD 1 is connected to (i.e. "file" too). If instead you say:

foo 2>&1 >file

it'll first connect FD 2 to whatever FD 1 was connected to (i.e. your terminal), then connnect FD 1 to "file".

(bash understands "|&" as shorthand for "2>&1 |" just like zsh does, incidentally, but it's best not to rely on it in scripts since it's not in the standard -- it means something different in mksh, for example.)

Anne M. Archibald said...

@Popup: In fact I used zsh for several years, but I eventually gave it up when, during an upgrade, I realized I did most of my shell-based computing on machines that didn't have it installed (and on which I didn't have root). So any zsh finger habits were only going to get me in trouble.

Anyway, bash's completion is better than it used to be; "gcc -" yields "Display all 699 possibilities? (y or n)", though unfortunately they are not annotated. The infrastructure to make this happen isn't just in bash; there's some scheme where ubuntu packages can specify their own completions so that bash can pick them up.

I'm undecided on the related feature where if you type a non-existent command bash will suggest a package you can install to get it; on the one hand it's a nice way to get that missing obscure-but-handy UNIX utility, but on the other it makes typos in commands quite slow to bring up another prompt.

Anne M. Archibald said...

@Adam Sampson: thanks for the |& syntax - for some reason I had that on my mental "don't work" list.

As for the reasoning behind the placement of the 2>&1, I can follow the algorithm you describe, but it really puzzles me why pipes and redirection to files should be treated so differently. I suppose bash's >( syntax, e.g. ls > >(grep -v foo), allows more redirection-like piping.

To be honest, I use shell scripts less and less - if I'm going to put it in a file, I might as well write in python, where there's less foot-shooting. On the other hand some of my shell one-liners are fairly horrible (shell for loops are very handy but also fairly ugly to look at).

mvc said...

My usual solution to bash annoyances is to talk the sysadmin into trying zsh themselves. :) So far I've been able to at least convince them all that's it's worthwhile to install it for me. Besides amazing tab completions for git and across SSH connections, the biggest win is replacing every possible find command with a file glob.

@PopUp: in zsh command >& file also works.

@Adam Sampson: echo foo |& rev gives me syntax error near unexpected token `&' in bash 3.2.39.

Anne M. Archibald said...

@mvc: I have a fair number of different sysadmins to deal with, all (of course) overworked, so it would take some serious effort. As for the completions, I get completion through ssh connections and git with bash out of of the box. And both redirections you describe - >& and |& - work fine. Perhaps the issue is that I'm using bash 4.0.33? I generally only switch from globs to find when my globs overflow the command-line, so a more powerful glob would not often come in handy.

There was an error in this gadget