3、进阶IO
>&n使用系统调用dup
(2)复制文件描述符n并把结果用作标准输出;
<&n标准输入复制自文件描述符n;
<&-关闭标准输入(键盘);
>&-关闭标准输出;
n<&-表示将n号输入关闭;
n>&-表示将n号输出关闭;
上述所有形式都可以前导一个数字,此时建立的文件描述符由这个数字指定而不是缺省的0或1。
如:
...2>file运行一个命令并把错误输出(文件描述符2)定向到file。
...2>&1运行一个命令并把它的标准输出和输出合并。
(严格的说是通过复制文件描述符1来建立文件描述符2,但效果通常是合并了两个流。
)
我们对2>&1详细说明一下:
2>&1也就是FD2=FD1,这里并不是说FD2的值等于FD1的值,因为>是改变送出的数据信道,也就是说把FD2的“数据输出通道”改为FD1的“数据输出通道”。
如果仅仅这样,这个改变好像没有什么作用,因为FD2的默认输出和FD1的默认输出本来都是monitor,一样的!
但是,当FD1是其他文件,甚至是其他FD时,这个就具有特殊的用途了。
请大家务必理解这一点。
exec0exec1>outfilename#打开文件outfilename作为stdout。
exec2>errfilename#打开文件errfilename作为stderr。
exec0<&-#关闭FD0。
exec1>&-#关闭FD1。
exec5>&-#关闭FD5。
问:
如果关闭了FD0、FD1、FD2,其后果是什么?
恢复FD0、FD1、FD2与关闭FD0、FD1、FD2有什么区别?
代码分别是什么?
打开了FD3~FD9,我们用完之后,你觉得是将他们关闭还是恢复?
下面是提示(例子来源于CU一帖子,忘记出处,来日再补上):
exec6>&22>ver
command>>dev/null&
exec2>&6#恢复FD2
4、简单举例
a、stdout和stderr都通过管道送给egrep了:
(lsyouno2>&1;lsyes2>&1)2>&1|egrep\*>file
(lsyouno2>&1;lsyes2>&1)|egrep\*>file
(lsyouno;lsyes)2>&1|egrep\*>file
这个例子要注意的就是:
理解命令执行顺序和管道“|”:
在命令执行前,先要进行重定向的处理,并将把nestedsub-shell的stdout接到egrep命令的stdin。
nestedsub-shell,在()中的两个命令加上(),可以看作一个命令。
其FD1已经连接到“|”往egrep送了,当遇到2>&1时,也就是FD2=FD1,即FD2同FD1一样,往管道“|”那边送。
b、没有任何东西通过管道送给egrep,全部送往monitor。
(lsyouno2>&1;lsyes2>&1)>&2|egrep\*>file。
虽然在()里面将FD2转往FD1,但在()外,遇到>&2,结果所有的都送到monitor。
请理解:
(lsyouno2>&1)1>&2|egrep\*>file##送到monitor
lsyouno2>&11>&2|egrep\*>file##送给管道“|”
lsyouno1>&22>&1|egrep\*>file##送到monitor
5、中阶例子
条件:
stderr通过管道送给egrep,正确消息仍然送给monitor(不变)
exec4>&1;(lsyouno2>&11>&44>&-;lsyes2>&11>
&44>&-)|egrep\*>file;exec4>&-
或者
exec4>&1;(lsyouno;lsyes)2>&11>
&44>&-|egrep\*>file;exec4>&-
如果加两个条件:
(1)要求cmd1和cmd2并行运行;
(2)将cmd1的返回值赋给变量ss。
则为:
exec3>&1;exec4>&1
ss=$(((lsyouno2>&11>&33>&-;echo$?
>&4)|egrep\*>file)4>&1)
exec3>&-;exec4>&-
说明:
exec3>&1;4>&1建立FD3,是用来将下面ls那条语句(子shell)中的FD1恢复到正常FD1,即输出到monitor,你可以把FD3看作最初始的FD1的硬盘备份(即输出到monitor);建立FD4,到时用作保存ls的返回值(echo$?
),你可以将FD4看作你考试时用于存放计算“echo$?
”的草稿纸;
(lsyouno2>&11>&33>&-;echo$?
>&4)大家还记得前面说的子shell和管道吧。
这条命令首先会继承FD0、FD1、FD2、FD3、FD4,它位于管道前,所以在运行命令前会先把子shell自己的FD1和管道“|”相连。
但是我们的条件是stderr通过管道送往egrep,stdout仍然输出到monitor。
于是通过2>&1,先把子shell的FD1的管道“送给”FD2,于是子shell中的stderr送往管道“|”;再通过1>&3,把以前的“硬盘备份”恢复给子shell的FD1,于是子shell中的FD1变成送到monitor了。
再通过3>&-,将3关闭;接着运行echo$?
,本来其输出值应该送往管道的,通过>&4,将输出送往“草稿纸”FD4,留以备用。
((lsyouno2>&11>&33>&-;echo$?
>&4)|egrep\*>file)于是,stderr通过管道送给egrep,stdout送给monitor,但是,还有FD4,它送到哪去了?
$(((lsyouno2>&11>&33>&-;echo$?
>&4)|egrep\*>file)4>&1)最后的4>&1,就是把FD4重定向到FD1。
但由于其输出在$()中,其值就赋给变量ss了。
最后一行关闭FD3、FD4。
6、高阶例子
命令cmd1,cmd2,cmd3,cmd4.如何利用单向管道完成下列功能:
1.所有命令并行执行。
2.cmd1和cmd2不需要stdin。
3.cmd1和cmd2的stdout定向到cmd3的stdin。
4.cmd1和cmd2的stderr定向到cmd4的stdin。
5.cmd3的stdout定向到文件a,stderr定向到屏幕。
6.cmd4的stdout定向到文件b,stderr定向到屏幕。
7.cmd1的返回码赋给变量s。
8.不能利用临时文件。
解决方法:
exec3>&1;exec4>&1
s=$(((((cmd11>&3;echo$?
>&4)|cmd2)3>
&1|cmd3>a2>&3)2>&1|cmd4>b)4>&1)
exec3>&-;exec4>&-
这个我一步步解释(好复杂,自己感觉看明白了,过一会再看,大脑仍然有几分钟空白~~~,没想到我也能看明白。
exec3>&1;exec4>&1前面的例子都有说明了,就是建立FD3,给cmd1恢复其FD1用和给cmd3恢复其FD2用,建立FD4,保存“echo$?
”输出值的“草稿纸”。
第一对括号:
(cmd11>&3;echo$?
>&4)和其后(第一个)管道。
在第一个括号(子shell)中,其FD1已经连到管道中了,所以用FD3将FD1恢复正常,不让他往管道跑;这里的cmd1没有stdin,接着将cmd1运行的返回码保存到FD4中。
第二对括号:
((cmd11>&3;echo$?
>&4)|cmd2)3>&1和其后(第二个)管道。
前面的FD1已经不送给cmd2了,FD2默认也不送过来,所以cmd2也没有stdin,所以在第二对括号里面:
cmd1和cmd2的stdout、stderr为默认输出,一直遇到“3>&1”为止。
请注意:
“3>&1”,先将第二对括号看出一个命令,他们遇到第二个管道时,其FD1连到管道“|”,由于“3>&1”的作用,子shell的FD1送给FD3使用,所以所有FD3的输出都“流往”cmd3,又由于继承关系(继承第一行的命令),FD3实际上就是cmd1和cmd2的stdout,于是“cmd1和cmd2的stdout定向到cmd3的stdin”
第三对括号:
(((cmd11>&3;echo$?
>&4)|cmd2)3>&1|cmd3>a2>&3)2>&1和其后的第三个管道。
cmd1和cmd2的stdout已经定向到cmd3的stdin,处理之后,cmd3>a意味着将其stdout送给a文件。
而2>&3的意思是:
恢复cmd3的错误输出为FD3,即送往monitor。
于是“cmd3的stdout定向到文件a,stderr定向到屏幕”。
如果没有“2>&3”,那么cmd3的错误输出就会干扰cmd1和cmd2的错误输出,所以它是必须的!
请注意第三对括号后的“2>&1”|,其子shell的FD1本来连接着管道“|”,但子shellFD1慷慨大方,送给了FD2,于是FD2连接着管道。
还记得前面的cmd1和cmd2吗?
他们的stderr一直没动了。
于是在这里,通过管道送给了第四个命令cmd4了。
即“cmd1和cmd2的stderr定向到cmd4的stdin”。
后面就比较简单了。
cmd4>b表示“cmd4的stdout定向到文件b,stderr定向到屏幕(默认)”
第四对括号:
((((cmd11>&3;echo$?
>&4)|cmd2)3>&1|cmd3>a2>&3)2>&1|cmd4>b)与其后的4>&1。
四对括号里面的FD1、FD2都处理完了。
但是还记得前面“echo$?
>&4”那块“草稿纸”吗?
“4>&1”的作用就是“将草稿纸上的内容送给monitor”,但是由于最外面还有$()将其“包着”。
于是其值赋给变量“s”。