这个命令会做什么?
exec 2>&1
从技术上讲,它会将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 上”。我知道,这完全不直观!
我看过的关于"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
是标准错误,1
是标准输出。当您运行程序时,通常会在标准输出中获得正常的输出,但任何错误或警告都通常会转到标准错误。如果您想将所有输出导管到文件中,首先将标准错误与标准输出结合使用并使用 2>&1
很有用。
exec 1>&2
后发生的情况。exec
是什么意思:exec
是Linux中的一个内置命令。与传统命令不同,它只是派生一个子shell进程,exec
可以改变当前的shell进程。
句柄 名称 描述
0 stdin 标准输入
1 stdout 标准输出
2 stderr 标准错误
让我们看一个例子:
$ pstree -p | grep 'term' | tr -d ' '
$ 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 中。
bash
$ ls /tmp/stdout
ls: cannot access '/tmp/stdout': No such file or directory
$ exec 1>/tmp/stdout
$ ls
$ pwd
$ echo "Are you ok"
$
此时,命令输出已经消失了。
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。
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
将标准输出 --->像@cma所说的那样,它将stderr放在stdout上。你可能想要这种行为是为了使用grep或任何其他实用程序来捕获仅出现在stderr上的输出。或者您可能只想将所有输出(包括stderr)保存到文件中以供以后处理。
exec 2>&1
:下面的例子是执行1000个HTTP请求并测量时间的测试用例。测试用例的输出应该发送到日志文件,但测量的时间应该发送到标准输出。exec 3>&1
。然后将标准输出重定向到文件log
:exec 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
exec 2>&1
的一个非常有用的应用是当你想要合并使用分号分隔的多个命令的stderr
和stdout
时。我的一个具体例子是在PHP中向popen
发送多条命令,并且我希望看到错误信息交错显示,就像在shell提示符下输入命令一样。
$ echo hi ; yikes ; echo there
hi
-bash: yikes: command not found
there
$
stderr
和stdout
合并,除了最后一个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
stdout
和stderr
的输出,这些输出会很好地交错在一起,就像在终端上执行三个由分号分隔的命令时看到的那样。