31

I want to make clear when does pipe | or redirection < > takes precedence in a command?

This is my thought but need confirmation this is how it works.

Example 1:

sort < names | head
The pipe runs first:  names|head   then it sorts what is returned from names|head

Example 2:

ls | sort > out.txt
This one seems straight forward by testing, ls|sort then redirects to out.txt

Example 3:

Fill in the blank?  Can you have both a < and a > with a | ???
Kairan
  • 5,342
  • 27
  • 65
  • 104
  • possible duplicate of [Shell Scripting - io redirecting - precedence of operators](http://stackoverflow.com/questions/12117375/shell-scripting-io-redirecting-precedence-of-operators) – n611x007 Apr 24 '15 at 11:05

5 Answers5

30

In terms of syntactic grouping, > and < have higher precedence; that is, these two commands are equivalent:

sort < names | head
( sort < names ) | head

as are these two:

ls | sort > out.txt
ls | ( sort > out.txt )

But in terms of sequential ordering, | is performed first; so, this command:

cat in.txt > out1.txt | cat > out2.txt

will populate out1.txt, not out2.txt, because the > out1.txt is performed after the |, and therefore supersedes it (so no output is piped out to cat > out2.txt).

Similarly, this command:

cat < in1.txt | cat < in2.txt

will print in2.txt, not in1.txt, because the < in2.txt is performed after the |, and therefore supersedes it (so no input is piped in from cat < in1.txt).

ruakh
  • 175,680
  • 26
  • 273
  • 307
  • OK so I understand what the output will be. However, understanding why the contents of out1.txt do not pipe to cat > out2.txt, other than the fact that out1.txt is not a command. ? – Kairan Oct 17 '12 at 20:10
  • Also if I do cat in1.txt > out1.txt > out2.txt then out2.txt ends up with the data from the cat cmd while out1 is empty? This just seems a bit odd – Kairan Oct 17 '12 at 20:19
  • 1
    @Kairan: `cat in.txt > out1.txt | cat > out2.txt` means `( cat in.txt > out1.txt ) | ( cat > out2.txt )`: it pipes the output of `cat in.txt > out1.txt` into `cat > out2.txt`. But `cat in.txt > out1.txt` doesn't *have* any output (because its output goes to `out1.txt`), so `cat > out2.txt` doesn't get any input. Do you see what I mean? – ruakh Oct 17 '12 at 20:43
  • @Kairan: In `cat in1.txt > out1.txt > out2.txt`, the `> out1.txt` is processed first (so STDOUT is redirected to `out1.txt`), and then it gets superseded by the `> out2.txt` (when STDOUT is *re*-redirected to `out2.txt`). The only thing that's odd about it is the the idea that anyone would try it. :-P – ruakh Oct 17 '12 at 20:44
  • 4
    The statement `But in terms of sequential ordering, | is performed first` is not correct. The piping and redirection are all done left to right. Consider `cd /foo 2>&1 > out.txt | sed 's/foo/bar/'` This dups the stderr of cd onto its stdout, then dups stdout onto out.txt then dups stdout to the pipe. – William Pursell Oct 17 '12 at 21:16
  • 2
    @WilliamPursell: Sorry, but you're simply mistaken. Argument from evidence: if that were true, then `cat foo > out1.txt > out2.txt` and `cat foo > out1.txt | cat > out2.txt` would be equivalent, because left-to-right processing would give the same result. But in point of fact, the former writes to `out2.txt` (because `> out2.txt` is done last, so wins out) while the latter writes to `out1.txt` (because `> out1.txt` is done after `| ...`, so wins out). Argument from authority: *[continued]* – ruakh Oct 17 '12 at 21:26
  • 2
    *[continued]* [§3.2.2 "Pipelines" of the *Bash Reference Manual*](http://www.gnu.org/software/bash/manual/bashref.html#Pipelines) explicitly says, "The output of each command in the pipeline is connected via a pipe to the input of the next command. That is, each command reads the previous command’s output. This connection is performed **before** any redirections specified by the command" (emphasis mine). – ruakh Oct 17 '12 at 21:28
  • I stand corrected, but `cat foo > out1.txt > out2.txt` is very different from `cat foo > out1.txt | cat > out2.txt`. The former consists of only 1 command being redirected twice, while the latter is two commands, with the second redirected once and the first redirected twice. You are correct that the `|` redirection occurs first, and is then superseded by the `>`. – William Pursell Oct 17 '12 at 21:52
  • @WilliamPursell: Sorry, I don't really understand your objection. Yes, the two commands consist of very different things; among other things, the latter launches two subshells, and two copies of `cat`, and it dupes STDIN at one point, all of which the former does not. My point was just that, if pipelines and redirections were handled in a single series, left-to-right, then the two commands would be equivalent: they'd give the same result, have the same ultimate effect, etc. No? – ruakh Oct 17 '12 at 22:14
  • 2
    My objection is merely that this particular example is not really appropriate, since the redirection to out2.txt is being applied to a different command. You are completely correct that the `|` redirections must occur first. – William Pursell Oct 17 '12 at 22:22
  • @WilliamPursell: The redirection to `out2.txt` in the second command wasn't the relevant part. I could just as well have written `cat foo > out1.txt | tee out2.txt >/dev/null`. The question was simply whether `> out1.txt` was performed before `| ...`; we both already agreed that `... |` was performed before `> out2.txt` (I because `|` precedes `>`, you because you believed that, quote, "piping and redirection are all done left to right"), so it didn't matter. It had no opportunity to be appropriate or not. – ruakh Oct 17 '12 at 22:34
  • Can someone explain what piping _before_ redirection entails when the left operand to the pipe is a filename as in the `names | head` part of `sort < names | head`? Clearly, the `head` operation is occurring _after_ the `sort` operation, and `head` does not actually operate on the `names` file itself. So what does it mean to say that `names` is piped to `head` before standard input to `sort` is redirected so that it comes from `names` instead of the keyboard or some other default? – Nicholas Cousar Jan 23 '22 at 05:49
  • @NicholasCousar: I think you've misunderstood something. You know how "2 \* 3 + 4" means "(2 \* 3) + 4", so it doesn't really contain "3 + 4"? Well, in the same way, `sort < names | head` means `{ sort < names; } | head`, so it doesn't really contain `names | head`. – ruakh Jan 23 '22 at 06:43
14

From man bash (as are the other quotes):

SHELL GRAMMAR
   Simple Commands
       A simple command is a sequence of optional variable assignments followed by
       blank-separated words and redirections, and terminated  by  a  control
       operator. The first word specifies the command to be executed, and is
       passed as argument zero.  The remaining words are passed as arguments
       to the invoked command.

       The return value of a simple command is its exit status, or 128+n if
       the command is terminated by signal n.

   Pipelines
       A pipeline is a sequence of one or more commands separated by one of
       the control operators | or |&.  The format for a pipeline is:

              [time [-p]] [ ! ] command [ [|⎪|&] command2 ... ]

In other words, you can have any number of redirections for a (simple) command; you can also use that as part of a pipeline. Or, put another way, redirection binds more tightly than pipe.

There are a couple of ways to get work around this (although they're rarely either necessary or aesthetic):

1. You can make a "compound command" and redirect into it:

 Compound Commands
   A compound command is one of the following:

   (list)  list is executed in a subshell environment (see
           COMMAND EXECUTION ENVIRONMENT below).  Variable
           assignments  and  builtin  commands  that  affect  the
           shell's environment do not remain in effect after the
           command completes.  The return status is the exit status of list.

   { list; }
          list  is  simply  executed  in the current shell environment.  list
          must be terminated with a newline or semicolon.  This is known as a
          group command. The return status is the exit status of list.  Note
          that unlike the metacharacters ( and ), { and } are reserved words
          and must occur where a reserved word is permitted to be recognized.
          Since they do not cause a word break, they must be separated from
          list by whitespace or another shell metacharacter.

So:

$ echo foo > input
$ { cat | sed 's/^/I saw a line: /'; } < input
I saw a line: foo

2. You can redirect to a pipe using "process substitution":

Process Substitution
   Process  substitution  is  supported on systems that support named pipes
   (FIFOs) or the /dev/fd method of naming open files.  It takes the form of
   <(list) or >(list).  The process list is run with its input or output
   connected to a FIFO or some file in /dev/fd.  The name of this file is
   passed as  an  argument  to  the  current  command  as the result of the
   expansion.  If the >(list) form is used, writing to the file will provide
   input for list.  If the <(list) form is used, the file passed as an argument
   should be read to obtain the output of list.

So:

 rici@...$ cat > >(sed 's/^/I saw a line: /') < <(echo foo; echo bar)
 I saw a line: foo
 rici@...$ I saw a line: bar

(Why the prompt appears before the output terminates, and what to do about it are left as exercises).

rici
  • 234,347
  • 28
  • 237
  • 341
6

This is pretty much what I understand after doing some reading (including ruakh's answer)

First of all, if you redirect multiple times, all the redirections are performed, but only the last redirection will take effect (assuming none of the earlier redirections cause error)

  • e.g. cat < in1.txt < in2.txt is equivalent to cat < in2.txt, unless in1.txt does not exist in which case this command will fail (since < in1.txt is performed first)

  • Similarly, with cat in.txt > out1.txt > out2.txt, only out2.txt would contain the contents of out2.txt, but since > out1.txt was performed first, out1.txt would be created if it doesn't exist.

What pipe does is connect the stdout of previous command to the stdin of the next command, and that connection comes before any other redirections (from Bash manual).

So you can think of

cat in1.txt > out1.txt | cat > out2.txt

as

cat in1.txt > pipe > out1.txt; cat < pipe > out2.txt

And applying the multiple redirection rule mentioned before, we can simplify this to

cat in1.txt > out1.txt; cat < pipe > out2.txt

Result: The content of in1.txt is copied to out1.txt, since nothing was written to pipe


Using another of [ruakh][3]'s example,
cat < in1.txt | cat < in2.txt

is roughly equivalent to

cat > pipe < in1.txt; cat < pipe < in2.txt

which is effectively

cat > pipe < in1.txt; cat < in2.txt

Result: This time something is written to the pipe, but since the second cat reads from in2.txt instead of pipe, only the content of in2.txt is printed out. If the pipe is in the middle of the same side (> or <) redirection, it will be ingored.

neo
  • 360
  • 3
  • 9
doubleDown
  • 8,048
  • 1
  • 32
  • 48
  • 1
    Re: "only the last redirection will take effect, e.g. `cat < in1.txt < in2.txt` is equivalent to `cat < in2.txt`": This is mostly true, but I do have a quibble: because `< in1.txt` *is* performed (albeit later superseded), the command `cat < in1.txt < in2.txt` will fail if `in1.txt` does not exist or cannot be read. – ruakh Oct 18 '12 at 13:32
  • Thanks for this clarification. Edited my answer to add this point. – doubleDown Oct 18 '12 at 19:36
3

It's a little unorthodox, but perfectly legal, to place the < anywhere you like, so I prefer this as it better illustrates the left-to-right data flow:

<input.txt sort | head >output.txt

The only time you cannot do this is with built-in control structure commands (for, if, while).

# Unfortunately, NOT LEGAL
<input.txt  while read line; do ...; done

Note that all of these are equivalent commands, but to avoid confusion you should use only the first or the last one:

<input.txt grep -l foobar
grep <input.txt -l foobar
grep -l <input.txt foobar
grep -l foobar <input.txt

Because the file name must always come directly after the redirection operator, I prefer to leave out the optional space between the < and the file name.

Mark Edgar
  • 4,707
  • 2
  • 24
  • 18
1

Corrections:

Example 1:

sort < names | head

In this case, input redirect runs first (names are sorted), then the result of that is piped to head.

In general you can read from left to right. The standard idiom works as follows:

  • Use of input redirection "<" tells the program reads from a file instead of stdin
  • Use of output redirection ">" tells the program to output to a file instead of stdout
  • Use of pipe "program_a | program_b" takes everything that would normally be output by program_a to stdout, and feeds it all directly to program_b as if it was read from stdin.
sampson-chen
  • 45,805
  • 12
  • 84
  • 81