![]() | 博主历时三年精心创作的《大数据平台架构与原型实现:数据中台建设实战》一书现已由知名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