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)
即可发现此命令的秘密。
系统会创建一个临时的文件描述符,然后将用以替代的进程的输出和这个文件描述符关联起来。
主要参考
- https://unix.stackexchange.com/questions/676683/what-does-the-output-of-ll-proc-self-fd-from-ll-dev-fd-mean
- https://unix.stackexchange.com/questions/93531/what-is-stored-in-dev-pts-files-and-can-we-open-them
- https://segmentfault.com/a/1190000040086046
- https://www.gnu.org/software/bash/manual/html_node/Redirections.html
- https://www.cnblogs.com/chengmo/archive/2010/10/20/1855805.html
- https://blog.csdn.net/cjfeii/article/details/10084343
- https://askubuntu.com/questions/229447/how-do-i-diff-the-output-of-two-commands
No Comments