129

I was looking at pre-commit hook and discovered the following line because I was wondering why I always got an empy file called 1 in my directory after doing a commit.

git status 2&>1 > /dev/null

I believe the intent was to write the following, and I corrected it.

git status 2>&1 > /dev/null

However, I was curious about what the following syntax does exactly, so I looked up the man page.

git status 2&>1

Here is the man page.

  Redirecting Standard Output and Standard Error
      This  construct allows both the standard output (file descriptor 1) and
      the standard error output (file descriptor 2) to be redirected  to  the
      file whose name is the expansion of word.

      There  are  two  formats  for  redirecting standard output and standard
      error:

             &>word
      and
             >&word

      Of the two forms, the first is preferred.  This is semantically equiva‐
      lent to

             >word 2>&1

However, this man page implies that the two are equivalent, which does not seem to be the case.

Can someone clarify the man page and explain exactly what is happening with this syntax?

7
  • I thought I saw this question somewhere... Maybe I'm mistaken.
    – user3117575
    Commented Jul 17, 2014 at 0:35
  • 1
    The order of symbols is different.
    – merlin2011
    Commented Jul 17, 2014 at 0:40
  • 1
    @devnull, I know that it is a typo, but I still wanted to know what it meant. :)
    – merlin2011
    Commented Jul 17, 2014 at 7:22
  • 3
    &> is the same as 2>&1 in bash, but it's not the same in other shells, like dash!
    – Lluís
    Commented Jul 31, 2014 at 8:25
  • 1
    vote to reopen - the duplicate is different (and git status 2>&1 is not the same as git status 2&>1)
    – M.M
    Commented Jan 23, 2017 at 12:08

4 Answers 4

160

The operators we are using here are:

  • > Syntax: file_descriptoropt > file_name
  • >& Syntax: file_descriptoropt >& file_descriptor
  • &> Syntax: &> file_name

If the file descriptor is omitted, the default is 0 (stdin) for input, or 1 (stdout) for output. 2 means stderr.

So we have:

  • >name means 1>name -- redirect stdout to the file name
  • &>name is like 1>name 2>name -- redirect stdout and stderr to the file name (however name is only opened once; if you actually wrote 1>name 2>name it'd try to open name twice and perhaps malfunction).

So when you write git status 2&>1, it is therefore like git status 2 1>1 2>1 , i.e.

  • the first 2 actually gets passed as an argument to git status.
  • stdout is redirected to the file named 1 (not the file descriptor 1)
  • stderr is redirected to the file named 1

This command should actually create a file called 1 with the contents being the result of git status 2 -- i.e. the status of the file called 2 which is probably "Your branch is upto-date, nothing to commit, working directory clean", presuming you do not actually track a file called 2.

9
  • 4
    Hello, I think the &> name is not equivalent to the 1>name 2>name, which is equivalent to the 1>name 2>&1. See the "Redirecting Standard Output and Standard Error" section in man bash
    – bwangel
    Commented Mar 19, 2017 at 2:31
  • 2
    @yundongxu the page you link to (which is also quoted in the question) says that &>name is the same as >name 2>&1 . Which is the same as 1>name 2>&1 because the > operator is defined that way. I'm not sure what you are trying to say.
    – M.M
    Commented Mar 19, 2017 at 2:49
  • 1
    In your answer, you said that "&>name means 1>name 2>name". But I think the 1>name 2>name and 1>name 1>&2 are not the same. I write a small script to prove it, you can view it here.
    – bwangel
    Commented Mar 20, 2017 at 13:04
  • 1
    @yundongxu I never claim that 1>name 1>&2 are the same. My only claim is that 2&>1 means 2 1>1 2>1 where the first 2 is an argument to the command. In your test script, compare git status 2&>1 with git status 2 1>1 2>1.
    – M.M
    Commented Mar 20, 2017 at 22:18
  • 23
    Late to the party, but for reference for future readers: &>file is equivalent to 1>file 2>&1. Doing 1>file 2>file will not work, as the file is opened twice under different file descriptors. The text in the file will be jumbled (and not really deterministic). To redirect both STDOUT and STDERR in a portable way, use >file 2>&1 (also, order is important, so the command suggested by OP would only redirect errors to the file and redirect standard output to /dev/null)
    – knittl
    Commented Oct 28, 2018 at 12:55
20

All about redirection in bash

What does &> do in bash?

Regarding the erroneous git status 2&>1 > /dev/null...

Can someone clarify the man page and explain exactly what is happening with this syntax?

Yes, I can. I'm also trying to understand a variety of behaviors and bits of bash syntax too, so let's go through the statement in question, and some examples in order to gain a better understanding together.

Quick Summary

  1. If you just want to understand the full meaning of the odd/erroneous syntax in question: git status 2&>1 > /dev/null, jump straight to section 1 below.
  2. If you're here just based on the title of the question itself: "What does &> do in bash?", jump straight down to section 2 below.
  3. If you want to have a quick-reference summary of proper redirection usage in bash, such as redirecting stderr to stdout via 2>&1, or redirecting stdout to a file via >file or 1>file, or redirecting stderr to a file via 2>file, or redirecting both stdout and stderr to a file via &>file or >file 2>&1, look just at section 3 below: "3. Summary of proper redirection in bash".
  4. If you just want to test your bash knowledge on weird, redundant, overriding, or otherwise non-standard usages of redirection, look at section 4 below.

1. Breakdown of the statement git status 2&>1 > /dev/null

In the statement

git status 2&>1 > /dev/null

there are 3 separate parts that break out like this (even though this is not intuitive nor obvious, especially considering that there is no space between the 2 and &>1 in 2&>1):

git status 2    # statement 1
&>1             # statement 2
> /dev/null     # statement 3

If you had the following, however, they are all very different than the above. We'll talk about these later in this answer:

# separates into **two separate statements**: `2` and `&>1`
2&>1  # `2` is NOT part of `&>1`

# these are all **single statements**, but mean very different things (more on
# these below)
2>&1
2>1
  1. Statement 1 (git status 2) runs git status 2. Here, 2 is a parameter passed to git status. Since it's not a valid option, git status assumes it to be a path, which can be a file or directory. So, git returns the status of all files named 2, or of all files within a directory named 2. As man git status shows, a better way to specify paths is to separate options from paths with --, like this:

    git status -- 2  # 2 is a file or folder here
    

    Obviously, that's not what you're trying to do. :)

  2. Statement 2 (&>1) redirects stdout and stderr to a file named 1. This is also obviously not what you are trying to do. :) The &>file syntax redirects all stdout and stderr to file. You can read about this in the bash manual online here, or by running man bash and and searching for the section named "Redirecting Standard Output and Standard Error". We'll go over this section of the manual more later.

    Note that &>1 (written as 2&>1--two separate statements: 2 and &>1, in your question) is not the same syntax nor concept as 2>&1. The latter, 2>&1, redirects file descriptor 2 (stderr) to file descriptor 1 (stdout), where the 3 primary file descriptors are:

    1. 0 = stdin
    2. 1 = stdout
    3. 2 = stderr
  3. Statement 3 (> /dev/null): this is where it starts to get tricky. This can also be written without the space, as >/dev/null. It redirects stdout (file descriptor 1) to the /dev/null Linux pseudofile, which discards any output written to it. Writing >/dev/null is exactly identical to 1>/dev/null, as file descriptor 1 (stdout) is implied as the default option if not specified, as the bash manual states. Read more about it in the "Redirecting Output" section of the bash manual here.

    It starts to get tricky here for two reasons:

    1. First, because the 1 means two different things: in &>1 the one is a file named "1". This is likely a mistake on the user's part, but that's what it is. In >/dev/null there is an implied 1 as in 1>/dev/null, and that refers to file descriptor 1, which is stdout.
    2. Second, it is tricky because you have redirection overrides going on, where the last one sticks. &>1 redirects both stdout and stderr to a file named "1", but 1>/dev/null then redirects stdout to the "/dev/null" file. The latter redirection of stdout overrides the former, so the end result is that stderr is redirected to a file named "1" and stdout is redirected to the "/dev/null" file. This could have also been written as:
      # redirect stderr to a file named "1", and redirect stdout to the file
      # named "/dev/null" to discard it.
      2>1 1>/dev/null
      
      # same thing
      &>1 > /dev/null
      

The above information is begging for more explanation, so let's explain &> more and then give a detailed summary of how to correctly do redirection in bash.

2. Just addressing the title of this question: "What does &> do in bash?"

As @Casey Jones implies in his (unfortunately) deleted answer here, I think the bash manual explains it best: https://www.gnu.org/software/bash/manual/bash.html#Redirecting-Standard-Output-and-Standard-Error

From it we learn that all 4 of these syntaxes are nearly identical, with the 1st being recommended over the 2nd, and the 3rd being the more-universal way to do it outside of bash:

# 4 ways in bash to redirect both stdout and stderr to `file`

# 1. recommended in bash
# Think of the `&` symbol here as meaning "1 AND 2", since it redirects
# both stdout (file descriptor 1) AND stderr (file descriptor 2) to `file`
&>file
# 2. works, but `file` may NOT expand to a number or to `-`. 
>&file

# 3. the universal way to do it in or outside of bash: first redirect stdout to
# file, and then redirect stderr to stdout
>file 2>&1
# 4. exact same as 3 above
1>file 2>&1

So, &>file means "redirect both stdout and stderr to file."

Here is how the manual phrases it:

3.6.4 Redirecting Standard Output and Standard Error

This construct allows both the standard output (file descriptor 1) and the standard error output (file descriptor 2) to be redirected to the file whose name is the expansion of word.

There are two formats for redirecting standard output and standard error:

&>word

and

>&word

Of the two forms, the first is preferred. This is semantically equivalent to

>word 2>&1

When using the second form, word may not expand to a number or ‘-’. If it does, other redirection operators apply (see Duplicating File Descriptors below) for compatibility reasons.

3. Summary of proper redirection in bash

File descriptor reminder:

  1. 0 = stdin
  2. 1 = stdout
  3. 2 = stderr

Good, proper-usage syntax you should use:

# 1. file descriptor redirection

# redirect stderr to stdout
2>&1
# redirect stdout to stderr
1>&2


# 2. redirection to a file

# 2.A. redirect stdout to `file`
>file
1>file  # (same thing; the 1 is implied above)

# 2.B. redirect stderr to `file`
2>file

# 2.C. redirect BOTH stdout and stderr to `file` (4 ways)
# Think of the `&` symbol in this 1st example as meaning "1 AND 2", since it 
# redirects both stdout (file descriptor 1) AND stderr (file descriptor 2)
# to `file`
&>file       # recommended in bash      <===
>&file       # not recommended
>file 2>&1   # universal and fine       <===
1>file 2>&1  # exact same as just above <===

Examples:

# print "hey 2 " to stdout.
# - Note: the `"%s "` format specifier gets applied to each input argument
#   thereafter. So, calling `printf "%s " "hello" "world"` is as though you had
#   called `printf "%s %s " "hello" "world"`.
printf "%s " "hey" 2

# redirect stdout to `file`; "file" now contains "hey 2 "
printf "%s " "hey" 2 >file
printf "%s " "hey" 2 1>file  # (same thing)

# redirect stderr to `file`; "file" remains empty since no stderr was printed
printf "%s " "hey" 2 2>file

# redirect BOTH stdout and stderr to `file`
printf "%s " "hey" 2 &>file
printf "%s " "hey" 2&>file        # don't do this! (same thing in this case, 
                                  # but looks awkward without the space before 
                                  # the `&`)
printf "%s " "hey" 2 >file 2>&1   # (same thing)
printf "%s " "hey" 2 1>file 2>&1  # (same thing)

4. "Weird" examples that we will present as "puzzles" and to teach and test understanding

# print "hey 2 " to stdout, redirecting stdout to a file named "1", then to "2",
# then to "3", then to "4", then to "5". Ultimately, `>5` overrides all
# previous stdout redirections, resulting in 5 files being created
# (named "1", "2", "3", "4", and "5"), with **only file "5"** containing
# the "hey 2 " text, and all other files being empty. Read the contents of all 
# files all at once with `grep '' *`.
printf "%s " "hey" 2 >1 >2 >3 >4 >5
# OR, exact same thing:
printf "%s " "hey" 2 1>1 1>2 1>3 1>4 1>5

# read the contents of all files so you can see that only file "5" above has
# "hey 2 " in it
grep '' *

# print "hey " to a file named "5", while also creating empty files
# named "1", "2", "3", and "4". Note: `21>1` is a bit nonsensical in this case.
# It redirects file descriptor 21 to a file named "1". That doesn't really do
# anything. All the redirections thereafter redirect file descriptor 1 (stdout)
# to a file with a number for a name, as specified. 
printf "%s " "hey" 21>1 1>2 1>3 1>4 1>5

# print "hey " to a file named "5", while creating empty files
# named "1", "2", "3", and "4". stderr gets redirected to the file named "1",
# but it also ends up empty since no stderr output was produced by this command.
printf "%s " "hey" 2>1 1>2 1>3 1>4 1>5

# Print some error output to a file named "1", since stderr gets redirected to
# it. Stdout gets ultimately redirected to a file named "5", but no stdout
# output is printed since `--invalid_arg` is not a valid argument. The stderr
# error message printed to the file named "1" is:
#       bash: printf: --: invalid option
#       printf: usage: printf [-v var] format [arguments]
printf --invalid_arg "%s " "hey" 2>1 1>2 1>3 1>4 1>5

# print "hey 2 " to a file named "5", while also creating empty files
# named "1", "2", "3", and "4". Initially, both stdout and stderr get
# redirected to the file named "1", via `&>1`, but then the stdout redirection
# gets overridden repeatedly until the last one that "sticks" is the stdout
# redirection to the file named "5" via `1>5`.
printf "%s " "hey" 2 &>1 1>2 1>3 1>4 1>5
printf "%s " "hey" 2&>1 1>2 1>3 1>4 1>5     # (same as above, but even more
                                            # awkward-looking)
printf "%s " "hey" 2&>1 >2 >3 >4 >5         # (same as above)
printf "%s " "hey" 2 &>1 >2 >3 >4 >5        # (same as above)
printf "%s " "hey" 2 >1 2>&1 >2 >3 >4 >5    # (same as above)
printf "%s " "hey" 2 1>1 2>&1 >2 >3 >4 >5   # (same as above; reminder: `1>1` 
        # redirects stdout (file descriptor 1) to a file named "1", whereas
        # `2>&1` redirects stderr (file descriptor 2) to stdout 
        # (file descriptor 1); make sure you understand that by now)

# (NOT the same as above! See this example & description previously a few
# examples up)
printf "%s " "hey" 2>1 1>2 1>3 1>4 1>5
15

&>word (and >&word redirects both stdout and stderr to the result of the expansion of word. In the cases above that is the file 1.

2>&1 redirects stderr (fd 2) to the current value of stdout (fd 1). (Doing this before redirecting stdout later in the line does not do what you might expect and will split the outputs instead of keeping them combined and is a very common shell scripting error. Contrast this to >word 2>&1 which combines the two fds into one sending to the same location.)

$ { echo stdout; echo stderr >&2; }
stdout
stderr
$ { echo stdout; echo stderr >&2; } >/dev/null
stderr
$ { echo stdout; echo stderr >&2; } >/dev/null 2>&1
$ 
{ echo stdout; echo stderr >&2; } 2>&1 >/dev/null
stderr

Not that those are, while similar looking, not the same thing.

git status 2&>1 > /dev/null is, in fact, actually running git status 2 with a redirection of &>1 (stdout and stderr to file 1). Almost certainly not what was intended. Your correction almost certainly is what was intended.

$ git init repro
Initialized empty Git repository in /tmp/repro/.git/
$ cd repro/
$ git status
# On branch master
#
# Initial commit
#
nothing to commit
$ ls
$ git status 2>&1
# On branch master
#
# Initial commit
#
nothing to commit
$ ls
$ git status 2&>1
$ ls
1
$ cat 1
# On branch master
#
# Initial commit
#
nothing to commit
1
  • Re. the ordering thing, the way I think of it is that A >& B redirects A to point to where B is currently pointing -- it doesn't redirect A's lines to become input lines for B or anything like that. In C terms, like doing = on FILE *
    – M.M
    Commented Jul 17, 2014 at 1:09
-2

In this answer, I believe the part about the 2 in 2>&1 redirecting 2 as input to git status is incorrect.

In a repo typing "git status" gives a different response than "git status 2." The former will list the untracked files.

"git status 2&>1" the output of "git status" to stdo and creates an empty file 1, where 1&2 will redirect were there an error. Confirmed when issued in non git repo, two instances of error written.

3
  • 2
    This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. Commented Feb 20, 2023 at 2:01
  • @JamesRisner The question is what the syntax 2&>1 does, the answer referenced gives a mostly correct response excepting one detail. By correcting the detail I am indeed further answering the functionality of 2&>1. This requires no clarification of the asker. Should I instead rephrase this with full explanation of the symbol, with reference to to the original post when mentioning the clarification?
    – Revots
    Commented Feb 21, 2023 at 21:20
  • 2
    It would definitely help you to reword this to sound less like a comment on another answer. If you can rewrite it such that if the other answer gets deleted in the future, your answer is still complete. Commented Feb 21, 2023 at 22:24

Not the answer you're looking for? Browse other questions tagged or ask your own question.