博主历时三年精心创作的《大数据平台架构与原型实现:数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行,点击《重磅推荐:建大数据平台太难了!给我发个工程原型吧!》了解图书详情,京东购书链接:https://item.jd.com/12677623.html,扫描左侧二维码进入京东手机购书页面。 |
Shell 重定向这部分知识比较琐碎,符号很多,各种操作符又有多种“变种”,所以会让人感觉很凌乱。本文试图对这部分内容做一些规范化的梳理,主要以 GNU 官方文档为准。
为了让本文更加实用,我们先把最重要的一些结论和建议放在前面,至于为什么要这样做,可以再仔细阅读本文后续的介绍。
&>
实现正常信息和错误信息的合并重定向&> /dev/null
丢弃所有输出信息0
,1
,2
,2>
,2>&1
,1>&2
这些操作符>
是 1>
的简写形式,>
这个“坑”给了1>
,2>
就没有简写形式了实际上,关于重定向,我们记住&>
这一个操作符就足够了,在实际应用中,我们几乎不需要在命令行中使用到0
,1
,2
这些标识符,以及它们和&
,>
之间的各种组合。但是,当出现了由它们组成的各种组合时,我们还是得要知道具体的含义,这就是本文后续要介绍的内容了。
首先,有必要“温习”一个基础知识点:在Linux上一切皆文件!所有的设备都可以使用一个文件来描述或代表,这一点在重定向操作中体现的尤为明显,例如:0
,1
,2
,/dev/null
都是文件,其中:0
代表文件/dev/stdin
,1
代表文件/dev/stdout
,2
代表文件/dev/stderr
。而这些文件又分别代表了某种设备:/dev/stdin
为标准输入设备,也就是键盘,所以:0
代表 [ 标准输入 ], 默认是键盘,1
即代表[ 标准输出 ] ,默认是屏幕,2
代表 [ 标准错误 ] ,默认也是屏幕,0
,1
,2
的官方称谓是:file descriptor - 文件描述符。以下是与重定向有关的符号汇总:
符号 | 示例 | 说明 |
---|---|---|
0 | N/A | 代表:[ 标准输入 ],对应设备文件:/dev/stdin ,默认指向:键盘 |
1 | N/A | 代表:[ 标准输出 ],对应设备文件:/dev/stdout ,默认指向:屏幕 |
2 | N/A | 代表:[ 标准错误 ],对应设备文件:/dev/stderr ,默认指向:屏幕 |
/dev/null | N/A | 一个特殊文件,代表一个空设备,重定向到该文件就意味着:不输出任何信息 |
> | command > file | 标准输出重定向 |
2> | command 2> file | 标准错误重定向 |
>> | command >> file | 标准输出重定向 (append模式) |
< | command < input | 标准输入重定向 |
>& | command >& file | 合并输出重定向 |
&> | command &> file | 合并输出重定向 |
&>> | command &>> file | 合并输出重定向 (append模式) |
2>&1 | command 2>&1 | 合并输出重定向 (将[ 标准错误 ] 输出到 [ 标准输出 ] 指向的目标,实现正常信息和错误信息合并输出至一处) |
1 >& 2 | command 1>&2 | 合并输出重定向 (将 [ 标准输出 ] 合并到 [ 标准错误 ] 输出指向的目标,实现正常信息和错误信息合并输出至一处) |
2&>1 | N/A | 错误代码,不存在该重定向操作符 |
1&>2 | N/A | 错误代码,不存在该重定向操作符 |
注意:文件标识符0
,1
,2
和重定向操作符>
、<
之间是不能有空格的,通常的解释是:1>
、 2>
是一个整体(整体作为一个符号),其实可以反过来看,当0
,1
,2
和>
、<
之间出现空格时,0
,1
,2
就会被优先解释为:当前文件夹下名为0
,1
,2
的文件!所以中间不能有空格!
# always remove the file "a-non-existing-file.txt" no matter if it exists!
rm -f a-non-existing-file.txt
# always create the file "an-existing-file.txt"
echo 'hello world!' > an-existing-file.txt
cat an-existing-file.txt # 输出:hello world!
cat a-non-existing-file.txt # 输出:cat: a-non-existing-file.txt: No such file or directory
cat an-existing-file.txt a-non-existing-file.txt # 输出:hello world! + cat: a-non-existing-file.txt: No such file or directory
下面是一组“平平无奇”的标准输出重定向测试,记住一点:1>
和>
一样的,>
是1>
的简化形式,1>
是一个整体,中间不得有空格。
# Test Case 1: 标准输出重定向 [ > ]
# 清空 output.txt 文件
> output.txt
# 将标准输出重定向到 output.txt 文件
cat an-existing-file.txt > output.txt
# 查看输出
cat output.txt # 输出:hello world!
# Test Case 2: 标准输出重定向 [ 1> ]
> output.txt
cat an-existing-file.txt 1> output.txt
cat output.txt # 输出:hello world
下面的这组测试很好地验证了标准错误与标准输出之间的差异,由于我们只是将标准错误重定向到了output.txt文件,所以只有错误信息才能被输出到该文件中,正常的命令输出信息(如cat an-existing-file.txt
时输出的hello world!
)是不会输出到output.txt文件中的。还有一点可以补充的是:既然>
这个“坑”给了1>
,所以2>
就没有简写形式了。
# Test Case 3: 标准错误重定向 [ 2> ]
> output.txt
cat an-existing-file.txt 2> output.txt
cat output.txt # 输出:空(无内容)
> output.txt
cat a-non-existing-file.txt 2> output.txt
cat output.txt # 输出:cat: a-non-existing-file.txt: No such file or directory
> output.txt
cat an-existing-file.txt a-non-existing-file.txt 2> output.txt
cat output.txt # 输出:cat: a-non-existing-file.txt: No such file or directory
注意:本节讨论的这些操作全部都是”错误“示范,执行结果也很难解释,本节内容主要提醒读者不要犯本节示例中出现的这些错误,同时为下一节的合并重定向做铺垫。
显然,很多时候,我们需要把所有的信息(正常的输出和错误输出)统一输出到文件中,如果是基于上面的知识积累,我们可能想的一种做法是:将标准输出和标准错误都重定向到一个文件,就像下面这样:
# Test Case 4: [ 标准输出 ] 和 [ 标准错误 ] 分别重定向到同一文件,信息会相互覆盖
> output.txt
cat an-existing-file.txt a-non-existing-file.txt 1> output.txt 2> output.txt
cat output.txt # 输出:cat: a-non-existing-file.txt: No such file or directory
# Test Case 5: [ 标准输出 ] 和 [ 标准错误 ] 分别重定向到同一文件,信息会相互覆盖
> output.txt
cat a-non-existing-file.txt an-existing-file.txt 1>output.txt 2>output.txt
cat output.txt # 输出:hello world! + isting-file.txt: No such file or directory
上述想法是对的,但却忽略了一个技术细节,那就是:1> output.txt
和 2> output.txt
是两次独立的重定向操作,从测试结果上分析看,两次重定向操作1> output.txt
和 2> output.txt
应该都是”生效“的,但是由于它们都向同一个文件写入数据,出现了覆盖对方数据的问题,所以这种方式完全不可取!也没有必要深究这里面的覆盖逻辑是什么,总之,这不是一种正确的做法,我们实际需要的是:[ 标准输出 ] + [ 标准错误 ] 合并重定向
为了实现[ 标准输出 ] + [ 标准错误 ] 合并重定向,Shell引入了专职的操作符:&>
、>&
、2>&1
,它们能将标准输出和标准错误合并到输出到指定的位置(不存在上述相互覆盖的问题)。
&>
是首选,&>
和 >&
两种形式等价 ,但有极微小的差异# Test Case 6: [ 标准输出 + 标准错误 ] 合并重定向 [ &> ]
> output.txt
cat an-existing-file.txt a-non-existing-file.txt &> output.txt
cat output.txt # 输出:hello world! + cat: a-non-existing-file.txt: No such file or directory
# Test Case 7: [ 标准输出 + 标准错误 ] 合并重定向 [ >& ]
> output.txt
cat an-existing-file.txt a-non-existing-file.txt >& output.txt
cat output.txt # 输出:hello world! + cat: a-non-existing-file.txt: No such file or directory
上述测试用例表明&>
和 >&
可相互替换,但是,在一种特殊情况下,两者会有差异,即:>&
后面不可接数字或连字符,否则会报错,也就是说:当我们想要重定向的文件名刚好是个数字或连字符时,就只能使用&>
!就是因为这样一种很特殊的情况,使得&>
的适用性高出了>&
“一丢丢”,所以GNU官方文档也因此建议用户优先使用&>
!以下是测试用例:
# Test Case 8: [ 标准输出 + 标准错误 ] 合并重定向 [ &> ]
> 3
cat an-existing-file.txt a-non-existing-file.txt &>3
cat output.txt # 输出:hello world! + cat: a-non-existing-file.txt: No such file or directory
# Test Case 9: [ 标准输出 + 标准错误 ] 合并重定向 [ >& ]
> 3
cat an-existing-file.txt a-non-existing-file.txt >&3 # 报错:-bash: 3: Bad file descriptor
cat output.txt # 输出:hello world!
&>
和 >&
可视作 2>&1
或 1>&2
的简化形式,但它们并不完全等价这里一定要结合重定向的目标来看。通常&>
和 >&
直接出现在命令和重定向文件之间,语法简洁,“重定向”的示意性很强,也没有任何歧义。但是,单独的 2>&1
操作短语只是说:将[ 标准错误 ] 输出合并到 [ 标准输出 ] 指向的目标,而此时 [ 标准输出 ] 指向的是什么并没有给用户提供显式设置的方式,而是要根据当时的上下文判定(可能是文件,也可能是屏幕),这就会导致:由于2>&1
出现的时机(在命令行中的位置)不同,产生的结果也将不同!这是 2>&1
要比 &>
和 >&
难以“拿捏”的地方,下面的例子(也是一个非常Tricky的例子)很好地诠释了这一点:
# Test Case 10: [ 标准错误 ]合并重定向到[ 标准输出 ]: [ 2>&1 ]
> output.txt
# [ 标准输出 ]先被重定向到了文件,而后[ 标准错误 ]被合并重定向到了[ 标准输出 ]所指向的位置,此时也是文件
# 所以最终[ 标准错误 ]和[ 标准输出 ]都将输出到文件
cat an-existing-file.txt a-non-existing-file.txt > output.txt 2>&1
cat output.txt # 输出:hello world! + cat: a-non-existing-file.txt: No such file or directory
# Test Case 11: [ 标准错误 ]合并重定向到[ 标准输出 ]: [ 2>&1 ]
> output.txt
# [ 标准错误 ]先被合并重定向到了[ 标准输出 ],注意,此时的[ 标准输出 ]还是屏幕,所以[ 标准错误 ]实际是被定向到了屏幕,
# 而后[ 标准输出 ]被重定向到了文件(注意:> 就是 1>),但是[ 标准错误 ]没有再改动,所以最终错误信息输出到了屏幕,正常信息保存进了文件
cat an-existing-file.txt a-non-existing-file.txt 2>&1 > output.txt
cat output.txt # 输出:hello world!
在上述两个对比测试中可以看到:由于 2>&1
出现的位置不同,导致了完全不同的重定向结果,具体的解释参考代码中的注释。
理解了 2>&1
就不难理解 1>&2
了,它将 [ 标准输出 ] 合并重定向到了 [ 标准错误 ] 指向的目标,同样,此时 [ 标准输出 ] 指向的是什么需要根据当时的上下文判定。
2&>1
或 1&>2
这种形式,如果错误地使用了它,会导致”怪异“行为对,没有!虽然有 &>
,但就是没有 2&>1
,如果你错误地使用了它,命令行可能不会报错,但执行结果会和预期严重不符,看上去行为非常”怪异“!以下又是一个非常Tricky的测试用例:
# Test Case 12: [ 标准输出 + 标准错误 ] 合并重定向 [ 2&>1 ]
> output.txt
cat an-existing-file.txt a-non-existing-file.txt > output.txt 2&>1
cat output.txt # 输出:空(无内容)
cat 1 # 输出:hello world! + cat: a-non-existing-file.txt: No such file or directory + cat: 2: No such file or directory
执行后:控制台和output.txt均无任何输出!但是会在当前目录生成一个名为1
的文件,生成内容为:
hello world!
cat: a-non-existing-file.txt: No such file or directory
cat: 2: No such file or directory
我们来分析一下导致上述怪异输出的原因:首先,[ 标准输出 ]先被重定向到了文件,然后2
被解释为了一个文件,给到cat去读取,但由于当前目录并没有这个文件,故cat命令(将会)报错:cat: 2: No such file or directory
,然后&>1
被解释为:[ 标准输出 + 标准错误 ] 合并重定向到了文件 1
,如此一来,整个输出内容就可以解释通了:
1
;cat an-existing-file.txt
时,标准输出了hello world!
;cat a-non-existing-file.txt
时,标准错误输出了cat: a-non-existing-file.txt: No such file or directory
;cat 2
时,标准错误输出了cat: 2: No such file or directory
参考资料
https://www.gnu.org/software/bash/manual/html_node/Redirections.html
https://www.redhat.com/sysadmin/linux-shell-redirection-pipelining