sh command: exec 2>&1

58

这个命令会做什么?

exec 2>&1 
7个回答

44

从技术上讲,它会将stderr复制到stdout中。

通常情况下,您不需要使用exec来执行此操作。使用文件描述符执行exec的更典型用法是指示要将文件分配给未使用的文件描述符,例如:

exec 35< my_input

顺便提一句,在将内容管道传输到文件时,声明的顺序很重要,因此:

ls > mydirlist 2>&1

这将有效,因为它将标准输出和错误输出都重定向到名为mydirlist的文件中,而命令

ls 2>&1 > mydirlist

将标准输出 stdout 而非标准错误 stderr 重定向到文件 mydirlist,因为在将 stdout 重定向到 mydirlist 之前,stderr 被复制到了 stdout。

编辑:这是 shell 左到右扫描的方式。因此,先读第二个语句为“将 stderr 复制到 stdout ”,然后再读第一个语句为“将 stdout 发送到文件 mydirlist ”,最后再读第二个语句为“将 stderr 复制到已设置的 stdout 上”。我知道,这完全不直观!


1
抱歉,我不理解这个。因此将stderr重定向到stdout,然后将stdout重定向到文件是行不通的,但是将stdout重定向到文件,然后将stderr重定向到stdout就可以了?看起来应该相反才对。 - Bjarke Freund-Hansen
非常反直覺。我已經嘗試這樣做了不知道多少次,並想知道為什麼它從未起作用。非常感謝。 - Bjarke Freund-Hansen
1
“将stderr复制到stdout”是问题所在 - 它并没有这样做。它获取stdout的文件描述符(文件描述符1)并为文件描述符2创建一个副本。因此,无论stdout在那个时刻指向哪里,现在stderr也指向那里。如果在此之后使用|xxx,则替换stdout的文件描述符 - 但是stderr仍然停留在原地,即stdout曾经去过的地方。 - ijw

36

我看过的关于"2>&1"是做什么的文章中较好的一篇是Bash One-Liners Explained, Part III: All about redirections

但是,这个问题上当前的回答未能提供的是为什么你想在一个普通的"exec"之后这样做。正如bash命令的man页面所解释的那样:"如果没有指定命令,则任何重定向都在当前shell中生效"。

我写了一个简单的脚本叫做out-and-err.py,它将一行输出写入stdout,另一行写入stderr:

#!/usr/bin/python
import sys
sys.stdout.write('this is stdout.\n')
sys.stderr.write('this is stderr.\n')

然后我用一个名为 out-and-err.sh 的 shell 脚本包装它,并添加了一个 "exec 2>&1":

#!/bin/bash
exec 2>&1
./out-and-err.py

如果我仅运行Python脚本,则标准输出和标准错误将是分开的:

$ ./out-and-err.py 1> out 2> err
$ cat out
this is stdout.
$ cat err
the is stderr.

但是如果我运行这个shell脚本,你会发现exec会处理标准错误输出(stderr)的所有内容:

$ ./out-and-err.sh 1> out 2> err
$ cat out
this is stdout.
this is stderr.
$ cat err
$

如果您的包装shell脚本不仅仅执行一个Python命令,并且您需要将所有输出组合到标准输出中,那么执行"exec 2>&1"将使这变得容易。


2
很棒的答案,其他人没有解释重定向在整个shell(或脚本)中是有效的!而且这个例子很清晰明了。 - caesarsol

14

它将标准错误与标准输出绑定在一起。

2 是标准错误,1 是标准输出。当您运行程序时,通常会在标准输出中获得正常的输出,但任何错误或警告都通常会转到标准错误。如果您想将所有输出导管到文件中,首先将标准错误与标准输出结合使用并使用 2>&1 很有用。


2
调用exec而没有命令并不一定是一个隐藏功能(它在bas文档中有说明),但对我来说还是很新奇的。有趣的事情.. - Don Johe
它不会“将stderr导向stdout”。你可以说它“将stderr绑定到stdout”或者“关联stderr和stdout”。数据被管道传输,文件描述符则不会。 - William Pursell
谢谢William,我已经把它改成了“ties”。 - Charles Ma

4
这几天我也遇到了这个问题,但现在我已经解决了。请允许我解释一下在CLI中输入exec 1>&2后发生的情况。
我想逐步摆脱这个问题,所以如果您已经了解这方面的知识,可以简略地阅读以节省时间。
  • exec是什么意思:
exec是Linux中的一个内置命令。与传统命令不同,它只是派生一个子shell进程,exec可以改变当前的shell进程。
什么是I/O重定向: 重定向是Linux中的一个功能,可以更改标准输入/输出设备。在Linux中,默认有三个文件描述符。 句柄 名称 描述 0 stdin 标准输入 1 stdout 标准输出 2 stderr 标准错误 让我们看一个例子:
  1. 打开一个新终端
  2. 获取终端进程ID $ pstree -p | grep 'term' | tr -d ' '
  3. 检查进程文件描述符。 $ sudo ls -l /proc/{pid}/fd bash $ pstree -p | grep -i 'terminal' | tr -d ' ' ||||-gnome-terminal-(6008)-+-bash(7641)-+-grep(8355) $ sudo ls -l /proc/7641/fd total 0 lrwx------ 1 alopex alopex 64 Oct 27 19:39 0 -> /dev/pts/3 lrwx------ 1 alopex alopex 64 Oct 27 19:39 1 -> /dev/pts/3 lrwx------ 1 alopex alopex 64 Oct 27 19:39 2 -> /dev/pts/3 lrwx------ 1 alopex alopex 64 Oct 27 19:39 255 -> /dev/pts/3 正如您所看到的,ls 列出了PID(6860)进程文件。首先它们都以数字(0、1、2)命名,其次它们都将链接文件链接到 /dev/pts/3,这意味着无论标准输入/输出/错误会显示在 pts3 中。
  4. 将标准输出更改为 /tmp/stdout bash $ ls /tmp/stdout ls: cannot access '/tmp/stdout': No such file or directory $ exec 1>/tmp/stdout $ ls $ pwd $ echo "Are you ok" $ 此时,命令输出已经消失了。
  5. 打开一个新终端,检查 proc 文件 bash $ sudo ls -l /proc/7641/fd total 0 lrwx------ 1 alopex alopex 64 Oct 27 19:39 0 -> /dev/pts/3 lrwx------ 1 alopex alopex 64 Oct 27 19:39 1 -> /tmp/stdout lrwx------ 1 alopex alopex 64 Oct 27 19:39 2 -> /dev/pts/3 lrwx------ 1 alopex alopex 64 Oct 27 19:39 255 -> /dev/pts/3 显然,我们可以注意到1(文件描述符)已经更改链接到 /tmp/stdout。如我们所期望的,标准输出转移到了 /tmp/stdout。
  6. 恢复重定向。 bash $ exec 1>&2 $ cat /tmp/stdout a.sh /home/alopex Are you ok $ sudo ls -l /proc/7641/fd total 0 lrwx------ 1 alopex alopex 64 Oct 27 19:39 0 -> /dev/pts/3 lrwx------ 1 alopex alopex 64 Oct 27 19:39 1 -> /dev/pts/3 lrwx------ 1 alopex alopex 64 Oct 27 19:39 2 -> /dev/pts/3 lrwx------ 1 alopex alopex 64 Oct 27 19:39 255 -> /dev/pts/3 再次,1(文件描述符)链接到 /dev/pts/3,我们可以再次看到输出。
    总结: exec 1>&2 将标准输出 --->

3

像@cma所说的那样,它将stderr放在stdout上。你可能想要这种行为是为了使用grep或任何其他实用程序来捕获仅出现在stderr上的输出。或者您可能只想将所有输出(包括stderr)保存到文件中以供以后处理。


1
一个实际的例子,何时使用exec 2>&1:下面的例子是执行1000个HTTP请求并测量时间的测试用例。测试用例的输出应该发送到日志文件,但测量的时间应该发送到标准输出。
为了实现这一点,标准输出被复制:exec 3>&1。然后将标准输出重定向到文件logexec 1>log。最后将标准错误重定向到标准输出:exec 2>&1。这意味着标准错误也将被发送到文件log,因为标准输出已经被重定向。在此之后,文件描述符3仍然可以用于将消息发送到脚本的标准输出,尽管其他所有内容都进入日志文件:printf ... >&3
#! /bin/bash

export LC_ALL=C
exec 3>&1
exec 1>log
exec 2>&1
set -eux

timestamp () { date +%s.%N; }

loops=${1:-1000}
t0=$(timestamp)
for n in $(seq "$loops")
do
  curl --silent --show-error --noproxy \* http://localhost:8000 &
done
wait
t1=$(timestamp)

printf '%d loops: %0.4f seconds\n' "$loops" "$(bc <<< "$t1 - $t0")" >&3

1

exec 2>&1的一个非常有用的应用是当你想要合并使用分号分隔的多个命令的stderrstdout时。我的一个具体例子是在PHP中向popen发送多条命令,并且我希望看到错误信息交错显示,就像在shell提示符下输入命令一样。

$ echo hi ; yikes ; echo there
hi
-bash: yikes: command not found
there
$ 

以下内容不会将stderrstdout合并,除了最后一个echo(因为yikes导致错误,所以这是无意义的):
echo hi ; yikes ; echo there 2>&1

我可以通过以下方式“费力地”获取合并输出:

echo hi 2>&1; yikes 2>&1; echo there 2>&1

如果你使用exec,它看起来更加清晰、不容易出错:

exec 2>&1 ; echo hi; yikes; echo there

您会得到stdoutstderr的输出,这些输出会很好地交错在一起,就像在终端上执行三个由分号分隔的命令时看到的那样。

(回显 hi; 回显 yikes; 回显 there)2>&1 - 并非在所有情况下都有用,因为那是一个子 shell,但在许多情况下很有用。执行 2>&1 很难撤消。 - ijw
您的解释非常棒,我向您致敬!只是,我认为您最后一部分的意思是:执行2>&1; echo hi; yikes; echo there。 - Dr Neo
谢谢 - 我已经更新了帖子 - 你是完全正确的 :) - drchuck

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