这个命令会做什么?
exec 2>&1
从技术上讲,它将标准错误复制或复制到标准输出上。
通常您不需要执行程序来执行此操作。exec 与文件描述符的更典型用法是指示您要将文件分配给未使用的文件描述符,例如
执行 35< 我的输入
顺便说一句,不要忘记管道到文件时的声明顺序很重要,所以
ls > mydirlist 2>&1
将起作用,因为它将 stdout 和 stderr 都指向文件 mydirlist,而命令
ls 2>&1 > mydirlist
仅将 stdout 而不是 stderr 定向到文件 mydirlist,因为在将 stdout 重定向到 mydirlist 之前,stderr 已成为 stdout 的副本。
编辑:这是外壳从左到右扫描的方式。因此,在说“将标准输出发送到 mydirlist”之前,请阅读第二个说“将标准错误复制到标准输出”。然后将第一个读作“将标准输出发送到文件 mydirlist”,然后再读到“将标准错误复制到我设置的标准输出上”。我知道。这完全不直观!
我看过的关于“2>&1”所做的更好的文章之一是Bash One-Liners Explained, Part III: All about redirections。
但是目前关于这个问题的答案未能提供的是为什么你想在一个简单的“exec”之后这样做。正如 exec 命令的 bash 手册页所解释的:“如果未指定命令,则任何重定向都会在当前 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 脚本,stdout 和 stderr 是分开的:
$ ./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
标准输出。当你运行一个程序时,你会在 stdout 中得到正常的输出,但任何错误或警告通常会转到 stderr。例如,如果要将所有输出通过管道传输到文件,首先将 stderr 与 stdout 与2>&1
.
就像@cma 说的那样,它把标准错误放在标准输出上。您可能想要此行为的原因是使用 grep 或任何其他实用程序来捕获仅出现在 stderr 上的输出。或者您可能只想将所有输出(包括 stderr)保存到文件中以供以后处理。
这些天我也很讨厌这个问题,但现在我跳出来了。所以,请允许我解释一下你exec 1>&2
在 CLI 中输入后会发生什么。
我想一点一点地解决问题,所以如果您已经知道知识,只需略读即可节省您的时间。
exec
:exec
是 Linux 中的内置命令。与传统的只 fork 一个子 shell 进程的命令不同,它exec
可以改变当前的 shell 进程。
什么是 I/O 重定向:重定向是 Linux 中的一项功能。有了它,您可以更改标准输入/输出设备。在 Linux 中,默认情况下有三个文件描述符。
Handle Name Description
0 stdin Standard input
1 stdout Standard output
2 stderr Standard error
让我看一个例子:
$ 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/stdoutbash
$ 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
制作标准输出--->标准错误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; echo yikes; echo there
如果执行由分号分隔的三个命令,您将得到很好的stdout
交错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