跳转到主要内容

Linux Bash Shell 输入输出重定向

为避免符号混淆,本文不使用 <必选参数> 来描述命令,而直接使用 参数

标准输入输出是什么

我们知道,程序都是读取什么,处理一下,然后输出什么。
于是 Linux 中将它们标准化了,分为 3 种。

名称 英文名称 文件描述符 操作符 实际上是怎么工作的
标准输入 STDIN 0 <<< /dev/stdin -> /proc/self/fd/0 ->  /dev/pts/0
标准输出 STDOUT 1 1> 、1>>>、 >> /dev/stdout -> /proc/self/fd/1 ->  /dev/pts/0
标准错误输出 STDERR 2 2> 、2>> /dev/stderr -> /proc/self/fd/2 ->  /dev/pts/0
  • 文件描述符,本质是非负整数,通常是小整数。它是一个索引,通过该索引可以找到对应的文件;
  • /proc/ 是一个特殊文件系统,内核通过它向进程报告各种信息。它主要用于有关进程的信息,因此名称为"proc[esses]";
  • /proc/self 是一个“神奇”的符号链接,它始终指向正在访问的进程;
  • /proc/self/fd/ 报告进程打开的文件。每个条目都是一个“魔术”符号链接,其名称是文件描述符,其目标是打开的文件。所以 0 1 2 是你的 3 个输入输出设备,并且输出到你的终端;
  • /dev/ 是设备文件的特殊目录,是系统中所有设备的抽象,比如你的 CPU、硬盘,因此名称为"dev[ice]";
  • /dev/std* 你可能知道 Linux 中一切皆文件,所以 /dev/std* 就是你的标准输出输出设备;
  • /dev/pts 是你的虚拟终端设备;

你可以在自己的系统中 ls -l /dev/stdin 看它最后指向哪里。
所以默认情况下,程序的 3 种输出都会输出到你的终端。
而你,我亲爱的 root,可以用一些符号来让这些东西输出到别的地方去。


标准输入输出重定向

管道符 |

| 这个迷人的小东西叫做“管道符”。
在 shell 中使用 |,可以让你在程序间创建一个“管道”,将左边的所有输出,送到右边的输入。

比如 ls | grep hello
可以将 ls 的输入重定向指 grep,这样 grep 就可以直接处理 ls 输出了,而我们将直接看到 grep 的标准输出。


大于符和小于符 > <

在这里,它们其实并不是大于和小于,而是一个箭头。
在 shell 中使用 > < 可以让你改变把程序的输入输出换一个地方。

我只想要重定向标准输出
> 可以让你把左边的标准输出,送到右边的输入。注意是标准输出,因为单独使用时等同于 1>

那要是我只想要重定向标准错误输出呢?
没问题,只需要使用 2> 即可。

比如 ls > what_is_in_here.txt 
BOOM! 我们的 what_is_in_here.txt 文件中有了一份当前目录文件的名单。

那要是 ls 2> what_is_in_here.txt 会怎么样呢?
WALA ! 文件列表显示在了我们的终端中,而 what_is_in_here.txt 中什么也没有。没错我们的程序正常执行了,并将标准输出输出到了你的终端,而错误输出当然什么也没有。


等等,我的 what_is_in_here.txt 刚刚是有东西的,怎么第二个命令清空了呢?
这个解释起来有点长,所以就忽略了。

所以如果你想要重定向并追加到文件尾部,而不是覆盖文件,你应该使用 >>
例如 ls >> my_file_list.txt ,当然也可以 2>>


要是我想要将标准输入和输出都重定向呢?
混乱来了!

我们有 >&&>,两者在单独使用时是等价的(bash 推荐 >&),但组合起来就不是了,因为他们的参数位置不同。
理所当然的 & 代表 and,于是它能将标准输出和标准错误输出同时重定向。
但要特别注意,>&&>是一个单独的命令是一个整体,就像 ls 不是 l 和 s,>& 也不是 > 和 &。

&>文件 等价于 >文件 2>&1 意思是把标准输出和标准错误输出都重定向到文件,且不能使用 2&>1


那我要是直接 2>1 呢? 
不行哦:

  • >,其实是 命令 文件描述符>文件2>1 实际上会将标准错误输出输出到 1 这个文件。
  • >& ,其实是 命令   文件描述符>&文件描述符 参数是一个文件描述符 ,例如 >&1  1 被认为是标准输出。
  • &> ,其实是 命令   &>文件名 参数是一个文件名,例如 &>1 但 1 实际上被认为是文件,你可以理解为 & 占据了文件描述符的位置。
    因为需要兼容复制文件描述符,所以这样设计,因为造成了太多混乱,所以强烈不推荐使用 &>。 


再举一点例子

  • 2>&1   意思是把标准错误输出重定向到标准输出;
  • 1>&2   意思是把标准输出重定向到标准错误;
  • 命令 2&>1 是错误的,等同于命令 2 1>1 2>1 ,第一个 2 被认为是 xxx 的参数,也就是 命令 2   &>1


晕掉了吗?如果你想要同时重定向并追加、、追加到文件尾部,而不是覆盖文件。
那么只有一种写法, &>> 等价于 >>文件 2>&1
zsh 中就可以反过来写,这就是为什么标题中强调了这是 Bash shell。


别走,还有最后一个,前面都是重定输出,而这是重定向标准输入。

< 可以让你把右边的东西,送到左边的标准输入。 

 比如 cat < what_is_in_here.txt
就会在你的标准输出中打印 what_is_in_here.txt 中的内容。


exec 绑定重定向

哦,我亲爱的 root,除了上面那些,其实你还可以绑定重定向。

比如

  • exec 7>&1 将标准输出和 /proc/self/fd/7 绑定。
  • exec 1>sdtout.txt 将接下来的所有标准输出都输入到 stdout.txt 中,接下来你再执行命令你的终端就什么也不会返回,而是返回到 stdout.txt 中。
    怎么恢复标准输出呢?
    如果你执行了 exec 7>&1 的话,你可以 exec 1>&7 ,然后 exec 7>&- (关闭 7 文件描述符)
    如果你没有,那么似乎只能重新打开终端了。



表格总结

表格摘自 https://blog.csdn.net/cjfeii/article/details/10084343

输出重定向

Command > filename 把标准输出重定向到一个新文件中
Command >> filename 把标准输出重定向到一个文件(追加)
Command > filename 2>&1 把标准输出和错误一起重定向到一个文件中
Command 2> filename 把标准错误重定向到一个文件中
Command 2>> filename 把标准错误重定向到一个文件中(追加)
Command >> filename 2>&1 把标准输出和错误一起重定向到一个文件中(追加)
Command > filename 2> /dev/null 标准输出定向到文件,屏蔽掉错误输出

输入重定向

Command < filename Command 命令以 filename 文件作为标准输入
Command < filename > filename2 Command 信不信以 filename 文件作为标准输入,以 filename 2  作为标准输出
Command << delimiter 从标准输入中读入,以  delimiter 为结束符。这就是 Bash 的 HereDoc 用法

绑定重定向

Command  >&m 把标准输出重定向到文件描述符 m 中,如 ls >&1 
Command m>&n 把往文件描述符 m 的输出重定向到文件描述符 n 上,2>71。再如上面的完整写法是 1>&m
Command <&- 关闭标准输入
Command 2>&- 关闭标准错误输出,和 2>/dev/null 有类似功效


Process Substitution 进程替换

除了标准输入输出,还有一招叫做 Process Substitution。
可以用命令替换掉本来应该用文件名的地方。

<(list) 或者 >(list)  不能有空格,否则会被当做重定向。

比如 diff 命令,本来应该是 diff 文件1 文件2 然后你就可以比较文件之间的差异了。

但是我要是想比较命令而不是文件呢?

那就可以这样:diff <(命令1) <(命令2)

使用 echo <(echo) <(echo) 即可发现此命令的秘密。
系统会创建一个临时的文件描述符,然后将用以替代的进程的输出和这个文件描述符关联起来。

主要参考