在Bash中,&>是什么意思?

117
我正在查看预提交钩子,并发现以下行,因为我想知道为什么每次提交后我的目录中总是会出现一个名为1的空文件。

我在查看预提交钩子时发现了下面这一行,因为我好奇为什么每次提交后,我的目录中总是会出现一个名为1的空文件。

git status 2&>1 > /dev/null

我认为原意是要写以下内容,我已经进行了更正。

git status 2>&1 > /dev/null

然而,我对以下语法的确切作用感到好奇,因此我查看了man页面。

git status 2&>1

这里是手册页面。

  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

然而,这个 man 页面暗示这两者是等同的,但实际上并非如此。

请问有人能够澄清一下这个 man 页面,并解释一下这个语法究竟在发生什么?


我记得在某个地方看到过这个问题...也许我记错了。 - user3117575
1
符号的顺序不同。 - merlin2011
1
@devnull,我知道这是一个打字错误,但我仍然想知道它的含义。 :) - merlin2011
3
在Bash中,“&>”与“2>&1”是相同的,但在其他Shell(例如dash)中并不相同。 - Lluís
1
重新开放投票 - 重复内容不同(而且 git status 2>&1 不同于 git status 2&>1 - M.M
显示剩余2条评论
4个回答

150

我们在这里使用的操作符是:

  • > 语法:file_descriptoropt > file_name
  • >& 语法:file_descriptoropt >& file_descriptor
  • &> 语法:&> file_name

如果省略文件描述符,则默认为输入使用0(标准输入),或输出使用1(标准输出)。2表示stderr。

因此我们有:

  • >name 表示 1>name -- 将标准输出重定向到名为name的文件中
  • &>name 相当于 1>name 2>name -- 将标准输出和标准错误重定向到名为name的文件中(然而name只会被打开一次;如果实际上写了1>name 2>name,它会尝试两次打开name,可能会出现故障)。

因此,当您编写git status 2&>1时,实际上相当于git status 2 1>1 2>1,即:

  • 第一个2实际上作为参数传递给git status
  • 标准输出重定向到名为1的文件(而不是文件描述符1)
  • 标准错误重定向到名为1的文件
该命令实际上应该创建一个名为1的文件,其内容是git status 2 的结果--即名为2的文件的状态,假设您实际上没有跟踪名为2的文件,则状态可能是“Your branch is up-to-date, nothing to commit, working directory clean”。请注意保留HTML标记。

4
你好,我认为&> name不等同于1>name 2>name,而是等同于1>name 2>&1。请参考man bash中的“Redirecting Standard Output and Standard Error”章节。 - bwangel
2
@yundongxu,您链接的页面(该页面也在问题中引用)说&>name>name 2>&1相同。这与>运算符的定义方式相同,因此与1>name 2>&1相同。我不确定您想表达什么意思。 - M.M
1
在你的回答中,你说"&>name意味着1>name 2>name"。但我认为1>name 2>name1>name 1>&2不是一样的。我写了一个小脚本来证明它,你可以在这里查看。 - bwangel
1
@yundongxu,我从未声称1>name 1>&2是相同的。我的唯一声明是2&>1意味着2 1>1 2>1,其中第一个2是命令的参数。在您的测试脚本中,请比较git status 2&>1git status 2 1>1 2>1 - M.M
21
虽然来晚了,但为了日后的读者参考:&>file 等同于 1>file 2>&1。使用 1>file 2>file 不会起作用,因为文件在不同的文件描述符下被打开两次。文件中的文本将混乱无序(而且不是真正确定的)。要以可移植的方式重定向 STDOUT 和 STDERR,请使用 >file 2>&1(此外,顺序很重要,所以 OP 建议的命令只会将错误重定向到文件,并将标准输出重定向到 /dev/null)。 - knittl
显示剩余3条评论

15
< p >&>word(并且>&word会将stdoutstderr都重定向到单词扩展结果中。在上述情况下,即文件1

2>&1stderr(fd 2)重定向到stdout(fd 1)的当前值。(在后面重定向stdout之前这样做不会产生预期结果,而是会拆分输出,这是一个非常常见的shell脚本错误。相比之下,>word 2>&1将两个fds合并为一个,并发送到同一位置。)

$ { 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

尽管看起来相似,但那些并不是同一件事情。

git status 2&>1 > /dev/null 实际上是在运行 git status 2 并将stdoutstderr重定向到文件1。几乎肯定不是原意。您的更正几乎肯定是原意。

$ 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

关于排序的问题,我的想法是 A >& B 重定向 A 指向 B 当前指向的位置 -- 它不会将 A 的行重定向为 B 的输入行或其他类似的操作。用 C 语言术语来说,就像对 FILE * 执行 = 操作。 - M.M

13

有关Bash中的重定向

&>在Bash中是什么意思?

关于错误的git status 2&>1 > /dev/null...

有人能澄清一下man手册,并解释这个语法究竟在发生什么吗?

可以的。我也在尝试理解各种行为和bash语法,让我们一起通过这个语句和一些例子来更好地了解。

快速摘要

  1. 如果你只想理解问题中奇怪/错误语法的完整含义:git status 2&>1 > /dev/null,请直接跳到下面的第1节
  2. 如果你只是基于问题标题来到这里:“在bash中&>是什么意思?”,请直接跳到下面的第2节
  3. 如果你想要一个快速参考bash中正确重定向用法的摘要,例如通过2>&1将stderr重定向到stdout,或通过>file1>file将stdout重定向到文件,或通过2>file将stderr重定向到文件,或通过&>file>file 2>&1同时将stdout和stderr重定向到文件,请查看下面的第3节:“3. bash中正确重定向的摘要”。
  4. 如果你只想测试你对重定向的奇怪、冗余、覆盖或其他非标准用法的理解,请查看下面的第4节

1. git status 2&>1 > /dev/null语句的解析

这个语句中:

git status 2&>1 > /dev/null

有三个独立的部分,分别如下(尽管这并不直观或明显,特别是考虑到在2&>12&>1之间没有空格):

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

如果你拥有以下内容,它们与上述内容都非常不同。我们将在本答案后面讨论这些内容。
# 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
      
上述信息需要更多解释,因此让我们更详细地解释&>,然后总结如何在bash中正确进行重定向。
2. 仅针对这个问题的标题:“在bash中&>是什么?” 正如@Casey Jones在他的(不幸的是)已删除的答案中所暗示的,我认为bash手册最好地解释了它:https://www.gnu.org/software/bash/manual/bash.html#Redirecting-Standard-Output-and-Standard-Error 从中我们得知,所有这4种语法几乎相同,第一种建议优于第二种,第三种是在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

因此,&>file 的意思是“将 标准输出标准错误输出 都重定向到 file 文件中。”

这是手册如何表述的:

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. bash中正确重定向的总结

文件描述符提醒:

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

你应该使用的良好、正确的语法:

# 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 <===

例子:

# 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. "奇怪"的例子,我们将它们呈现为 "谜题",以便教授和测试理解

# 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

-1

在这个答案中,我认为关于2在2>&1重定向2作为输入到git status的部分是不正确的。

在一个repo中键入“git status”会给出与“git status 2”不同的响应。前者将列出未跟踪的文件。

“git status 2&>1”将“git status”的输出重定向到stdo并创建一个空文件1,其中1&2将重定向到错误(如果有)。在非git repo中发出时,写入两个实例的错误已得到确认。


1
这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案 - James Risner
@JamesRisner 这个问题是关于语法2&>1的作用,参考答案给出了一个大部分正确的回答,但有一个细节需要纠正。通过更正这个细节,我确实进一步回答了2&>1的功能。这不需要对提问者进行澄清。我是否应该重新表述并全面解释这个符号,并在提到澄清时引用原始帖子? - Revots
2
将其重新表述,使其听起来不像是对另一个答案的评论,这肯定会有所帮助。如果您能重写它,使得即使将来删除了其他答案,您的答案仍然是完整的。 - James Risner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接