这是Bash手册的重定向部分中的错字吗?


13
Note that the order of redirections is significant.  For example, the command

          ls > dirlist 2>&1

   directs both standard output and standard error to the file dirlist, 
   while the command

          ls 2>&1 > dirlist

   directs  only  the  standard  output  to  file  dirlist,  because the 
   standard error was duplicated from the standard output before the standard
   output was redirected to dirlist.

现在,最后一部分让我感到困惑。在这种情况下,任何标准错误都将被打印到终端,而任何STDOUT都将进入目录列表文件。那样会发生,但这不是我理解手册的方式。

似乎应该说“因为在将标准输出重定向到目录列表之后,从标准输出中复制了标准错误”。如果在将STDOUT定向到文件之前将STDERR发送到STDOUT,那么该文件是否不包含STDOUT AND STDERR?

有人可以帮我清理一下吗?我的阅读理解能力差吗?在这种情况下,重复一词的使用对我来说有点奇怪。也许那让我失望。



1
混合“按值”与“按引用”的操作的经典案例。复制文件描述符时,它是按值操作。在编程中,a = 1; b = a; a = 2您期望a == 2 && b == 1是真实的。重定向2>&1b = a分配类似-它是按值而不是引用。 2>&1不会将文件描述符2永久地保存到文件描述符1中-它们仍然是2个不同的文件描述符,它们恰好指向同一文件。
jw013

Answers:


23

复制实际上是这里的重要部分。

让我们看一下重定向之前文件描述符的去向。这通常是当前端子,例如:

STDOUT ---> /dev/pts/1
STDERR ---> /dev/pts/1

现在,如果我们在ls -l不进行重定向的情况下进行呼叫,则输出和错误消息将转到下方的终端/dev/pts/1

如果我们首先将重定向STDOUT到文件(ls -l > dirlist),则如下所示:

STDOUT ---> /home/bon/dirlist
STDERR ---> /dev/pts/1

当我们然后重定向STDERR到一个重复STDOUT的文件描述符(ls -l > dirlist 2>&1),STDERR进入的副本/home/bon/dirlist

STDOUT ---> /home/bon/dirlist
STDERR ---> /home/bon/dirlist

如果我们首先重定向STDERR到的STDOUT文件描述符(ls -l 2>&1)的副本,请执行以下操作:

STDOUT ---> /dev/pts/1
STDERR ---> /dev/pts/1

并且然后 STDOUT到一个文件中(ls -l 2>&1 > dirlist),我们会得到这样的:

STDOUT ---> /home/bon/dirlist
STDERR ---> /dev/pts/1

在这里,STDERR仍然要去码头。

您会看到手册页中的顺序是正确的。


测试重定向

现在,您可以自己进行测试。使用ls -l /proc/$$/fd/,您可以看到STDOUT(使用fd 1的)和STDERR(使用fd 2的)当前进程的位置:

$ ls -l /proc/$$/fd/
total 0
lrwx------ 1 bon bon 64 Jul 24 18:19 0 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 18:19 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 07:41 2 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 18:19 255 -> /dev/pts/1

让我们创建一个小的shell脚本,该脚本显示文件描述符指向的位置。这样,我们总是在调用时获得状态ls,包括从调用shell进行的任何重定向。

$ cat > lookfd.sh
#!/bin/sh
ls -l /proc/$$/fd/
^D
$ chmod +x lookfd.sh

(使用CtrlD,您发送文件结尾,因此停止cat从中读取命令STDIN。)

现在,使用不同的重定向组合来调用此脚本:

$ ./lookfd.sh 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:08 0 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:08 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:08 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:08 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh > foo.out
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:10 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:10 1 -> /home/bon/foo.out
lrwx------ 1 bon bon 64 Jul 24 19:10 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:10 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh 2>&1 > foo.out
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:10 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:10 1 -> /home/bon/foo.out
lrwx------ 1 bon bon 64 Jul 24 19:10 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:10 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh > foo.out 2>&1
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:11 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:11 1 -> /home/bon/foo.out
l-wx------ 1 bon bon 64 Jul 24 19:11 2 -> /home/bon/foo.out
lr-x------ 1 bon bon 64 Jul 24 19:11 255 -> /home/bon/lookfd.sh

您会看到文件描述符1(用于STDOUT)和2(用于STDERR)。为了娱乐,您还可以重定向STDIN并查看结果:

$ ./lookfd.sh < /dev/zero
total 0
lr-x------ 1 bon bon 64 Jul 24 19:18 0 -> /dev/zero
lrwx------ 1 bon bon 64 Jul 24 19:18 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:18 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:18 255 -> /home/bon/lookfd.sh

(留给读者的问题是:文件描述符255指向哪里?;-))


+1-绝佳答案。极其出色的文字和出色的例子。谢谢!!!
slm

我知道,我认为我的误解是,对于所有后续命令,重定向都是持久的,因此该行其余部分的所有STDERR都将转至STDOUT。
Gregg Leventhal 2013年

2

不,手册是正确的。

如果最初有1个指向终端,又有2个指向终端,则:

command  2>&1   1>somewhere

重定向评估将从左到右进行。

因此它将首先进行评估2>&1,然后首先将fd 1所指向的内容(即the terminal通常为/ dev / tty的文件描述符)复制到fd中2

因此,fd 2现在指向fd 1过去指向(the terminal)的位置

然后,它会评估1>somewhere零件,因此将复制somewherefd中的文件描述符1(因此,fd 1现在指向somewhere,而fd 2仍然指向the terminal

因此它确实确实将1打印到“某处”,并将2打印到终端,因为2在1之前已从1重复。

其他命令:

command  1>somewhere 2>&1

首先将fd重定向1somewhere,然后将该相同的引用复制到fd 2中,因此最后2也指向somewhere。但是从现在开始它们就没有“链接”。每个仍然可以分别重定向。

例如:

command  1>somewhere 2>&1
exec 2>/dev/null

在那之后,fd 1指向somewhere,并且fd 2指向/dev/null

fd的常用名称为1STDOUT(标准输出),fd的常用名称为2STDERR(标准错误,因为它通常用于显示错误而不干扰STDOUT)


@ Michael-mrozek:感谢您的编辑,但是我坚持说“复制”而不是“重复”为“重复”,可能使人们相信,从现在开始,两者都是“同一件事”,这是不正确的。例如: cmd 1>somewhere 2>&1 ; exec 2>/dev/nullexec之后,只有2个已重定向到/ dev / null(1仍将转到“某处”)。我确实需要帮助,想出一种方法来说“ 1指向什么”而不是“ fd 1”,但是……这也令人困惑……
Olivier Dulac

1
我不确定你是什么意思; 您就是将其从“复制”更改为“复制”的人。我所做的只是大写和格式化内容,我一字不

h ... ^^对不起。我再次编辑以重新制定格式,以使复制的内容更精确^^
Olivier Dulac

1

我认为这里令人困惑的部分是误解了将stderr重定向到stdout实际上连接了两个流。

一个完全合理的想法,但是在编写时发生的事情2>&1是stderr,它对stdout正在写入的内容进行了监视,并且自己写入了同一位置。因此,如果您随后告诉stdout去其他地方写,它对已经移动的stderr的目的地没有影响。

我认为这有点违反直觉,但这就是它的工作原理。首先设置您要写的位置,然后告诉每个人“抄袭我”。希望能澄清...


0

复制...

固然重要,但从某种意义上讲,它是造成许多混乱的根源。这真的很简单。这个答案仅仅是“激进”的例证。

可接受的答案是好的,但是太长了,它强调“复制”。

Q明智地以:

在这种情况下,重复一词使用对我来说有点奇怪。也许那让我失望。

我使用bash表示法,并将变量“一个”和“两个”定义为文件句柄“ 1”和“ 2”。(输出)重定向操作符>是一个赋值=&$表示“价值”。

man bash示例(添加了默认的“ 1”)

ls 1>dirlist 2>&1      # both to dirlist
ls 2>&1 1>dirlist      # 1 to dirlist, 2 stays on tty/screen 

成为:

one=dirlist  two=$one

two=$one   one=dirlist

甚至这对我和其他一些我来说都不是自动的。第一行与您在一起,$one并且$two都包含“ dirlist”。当然。

第二行以无用的分配开始。根据定义,两者都以“ TTY”(有点象征性)作为方向开始;此赋值不会更改任何值,并且与文件句柄一样,使用变量也不会神奇地链接任何内容。变量two不受以下影响one=dirlist。当然不是。

这里的Sombody(6年前)建议使用“指向”,而不是“复制”或“重复”,然后才意识到:这也会造成混淆。

甚至不需要这种重复或指针语义。也许是&符需要更多的注意。“值”运算符/令牌/任何值。

如果-并且仅当-您正在寻找一种在控制台上获得令人惊讶的工作编号的方法,然后显示“完成”消息以及名为“ 2”的文件作为奖励,那么您可以:

ls 1>2& 2>/dev/null

它自然读取为“ copy” /“ duplicate” 1到2,然后一起读为null。但是这个想法是错误的,语法也是错误的。(但没有语法错误,这是有效的)

正确的计划方法是将二者之一重定向为null,然后将OTHER重定向到SAME位置:

ls 1>/dev/null 2>&1
# or 
ls 2>/dev/null 1>&2

(前面的“ 1”可以省略)

(好的,按A记号不太长,但是列表太多-或:视觉效果很好,解释不太好)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.