>与< 差在哪?
1. 文件描述符(fd, File Descriptor)
谈到I/O redirection
,不妨先让我们认识一下File Descriptor
(fd
,文件描述符)。
进程的运算,在大部分情况下,都是进行数据(data)的处理,
这些数据从哪里,读进来?又输出到哪里呢?
这就是file descriptor(fd)的功用了。
在shell的进程中,最常使用的fd
大概有三个,分别为:
- 0:standard Input (
STDIN
) - 1: standard output(
STDOUT
) - 2: standard Error output (
STDERR
)
在标准情况下,这些fd分别跟如下设备(device)关联:
stdin
(0): keyboardstdout
(1): monitorstderr
(2): monitor
Tips:
linux中的文件描述符(fd)用整数表示。
linux中任何一个进程都默认打开三个文件,
这三个文件对应的文件描述符分别是:0, 1, 2;
即stdin, stdout, stderr.
我们可以用如下命令测试一下:
很明显,mail
进程所读进的数据,就是从stdin
也就是keyboard读进的。
不过,不见得每个进程的stdin
都跟mail
一样
从keyboard
读进,因为进程的作者可以从文件参数读进stdin
,
如:
但,要是cat
之后没有文件参数则如何呢?
哦, 请你自己玩玩看…^_^
Tips:
请留意数据输出到哪里去了,
最后别忘了按ctrl+d
(^d
), 退出stdin输入。
至于stdout
与stderr
,嗯…等我有空再续吧…^_^
还是,有哪位前辈来玩接龙呢?
相信,经过上一个练习后,
你对stdin
与stdout
应该不难理解了吧?
然后,让我们看看stderr
好了。
事实上,stderr
没什么难理解的:
说白了就是“错误信息”要往哪里输出而已…
比方说, 若读进的文件参数不存在的,
那我们在monitor上就看到了:
|
|
若同一个命令,同时成生stdout
与stderr
呢?
那还不简单,都送到monitor来就好了:
okay, 至此,关于fd及其名称、还有相关联的设备,
相信你已经没问题了吧?
2. I/O 重定向(I/O Redirection)
那好,接下来让我们看看如何改变这些fd的预设数据通道。
- 用
<
来改变读进的数据通道(stdin),使之从指定的文件读进。 - 用
>
来改变输出的数据通道(stdout,stderr),使之输出到指定的文件。
2.1 输入重定向n<
(input redirection)
比方说:
就是从my.file读入数据
|
|
则是从/etc/passwd读入…
这样一来,stdin将不再是从keyboard读入,
而是从指定的文件读入了…
严格来说,<
符号之前需要指定一个fd的(之前不能有空白),但因为0是<
的预设值,因此,<
与0<
是一样的*。
okay,这样好理解了吧?
那要是用两个<
,即<<
又是啥呢?
这是所谓的here document
,
它可以让我们输入一段文本,
直到读到<<
后指定的字符串。
比方说:
这样的话, cat
会读入3个句子,
而无需从keyboard读进数据且要等到(ctrl+d, ^d)结束输入。
2.2 重定向输出>n
(output redirection)
当你搞懂了0<
原来就是改变stdin
的数据输入通道之后,
相信要理解如下两个redirection就不难了:
1>
#改变stdout的输出通道;2>
#改变stderr的输出通道;
两者都是将原来输出到monitor的数据,
重定向输出到指定的文件了。
由于1是>
的预设值,
因此,1>
与>
是相同的,都是改变stdout
.
用上次的ls的例子说明一下好了:
这样monitor的输出就只剩下stderr
的输出了,
因为stdout
重定向输出到文件file.out去了。
|
|
这样monitor就只剩下了stdout
,
因为stderr
重定向输出到文件file.err了。
|
|
这样monitor就啥也没有了,
因为stdout
与stderr
都重定向输出到文件了。
呵呵,看来要理解>
一点也不难啦是不? 没骗你吧? ^_^
不过有些地方还是要注意一下的。
|
|
假如stdout
(1)与stderr
(2)都同时在写入file.both的话,
则是采取”覆盖”的方式:后来写入覆盖前面的。
让我们假设一个stdout
与stderr
同时写入到file.out的情形好了;
- 首先
stdout
写入10个字符 - 然后
stderr
写入6个字符
那么,这时原本的stdout
输出的10个字符,
将被stderr
输出的6个字符覆盖掉了。
那如何解决呢?所谓山不转路转,路不转人转嘛,
我们可以换一个思维:
将stderr
导进stdout
或者将stdout
导进到stderr
,
而不是大家在抢同一份文件,不就行了。
bingo就是这样啦:
- 2>&1 #将
stderr
并进stdout
输出 - 1>&2 或者 >&2 #将
stdout
并进stderr
输出。
于是,前面的错误操作可以改写为:
这样,不就皆大欢喜了吗? ~~~ ^_^
不过,光解决了同时写入的问题还不够,
我们还有其他技巧需要了解的。
故事还没有结束,别走开广告后,我们在回来….
2.3 I/O重定向与linux中的/dev/null
okay,这次不讲I/O Redirection, 请佛吧…
(有没有搞错?网中人
是否头壳烧坏了?…)嘻~~~^_^
学佛的最高境界,就是”四大皆空”。
至于是空哪四大块,我也不知,因为我还没有到那个境界..
这个“空”字,却非常值得反复把玩:
—色即是空,空即是色
好了,施主要是能够领会”空”的禅意,那离修成正果不远了。
在linux的文件系统中,有个设备文件: /dev/null
.
许多人都问过我,那是什么玩意儿?
我跟你说好了,那就是”空”啦。
没错空空如也的空就是null了…
请问施主是否忽然有所顿悟了呢?
然则恭喜了。
这个null在 I/O Redirection中可有用的很呢?
- 将fd
1
跟fd2
重定向到/dev/null去,就可忽略stdout, stderr的输出。 - 将fd
0
重定向到/dev/null,那就是读进空(nothing).
比方说,我们在执行一个进程时,会同时输出到stdout与stderr,
假如你不想看到stderr(也不想存到文件), 那就可以:
若要相反:只想看到stderr呢?
还不简单将stdout,重定向的/dev/null就行:
那接下来,假如单纯的只跑进程,而不想看到任何输出呢?
哦,这里留了一手,上次没讲的法子,专门赠与有缘人… ^_^
除了用 >/dev/null 2>&1
之外,你还可以如此:
Tips:
将&>换成>&也行!
2.4 重定向输出append (>>
)
okay? 请完佛,接下来,再让我们看看如下情况:
看来,我们在重定向stdout或stderr进一个文件时,
似乎永远只能获得最后一次的重定向的结果.
那之前的内容呢?
呵呵,要解决这个问题,很简单啦,将>
换成>>
就好了;
如此一来,被重定向的文件的之前的内容并不会丢失,
而新的内容则一直追加在最后面去。so easy?…
但是,只要你再次使用>
来重定向输出的话,
那么,原来文件的内容被truncated(清洗掉)。
这是,你要如何避免呢?
—-备份, yes,我听到了,不过,还有更好的吗?
既然与施主这么有缘分,老衲就送你一个锦囊妙法吧:
那,要如何取消这个限制呢?
哦,将set -o
换成 set +o
就行了:
再问:那有办法不取消而又“临时”改写目标文件吗?
哦,佛曰:不可告也。
啊,~开玩笑的,开玩笑啦~^_^,
哎,早就料到人心是不足的了
留意到没有:
在>
后面加个|
就好,
注意: >
与|
之间不能有空白哦…
2.5 I/O Redirection的优先级
呼….(深呼吸吐纳一下吧)~~~ ^_^
再来还有一个难题要你去参透呢:
嗯?注意到没有?
—怎么最后那个cat命令看到file是空的呢?
why? why? why?
前面提到:$cat < file > file
之后,
原本有内容的文件,结果却被清空了。
要理解这个现象其实不难,
这只是priority的问题而已:
在IO Redirection中, stdout与stderr的管道先准备好,
才会从stdin读入数据。
也就是说,在上例中,>file
会将file清空,
然后才读入 < file
。
但这时候文件的内容已被清空了,因此就变成了读不进任何数据。
哦,~原来如此~^_^
那…如下两例又如何呢?
嗯…同学们,这两个答案就当练习题喽,
下课前交作业。
Tips:
我们了解到>file
能够快速把文件file清空;
或者使用:>file
同样可以清空文件,:>file
与>file
的功能:
若文件file存在,则将file清空; 否则,创建空文件file (等效于touch file
);
二者的差别在于>file
的方式不一定在所有的shell的都可用。
exec 5<>file; echo "abcd" >&5; cat <&5
将file文件的输入、输出定向到文件描述符5,
从而描述符5可以接管file的输入输出;
因此,cat <>file
等价于cat < file
。而
cat < file >>file
则使file内容成几何级数增长。
好了, I/O Redirection也快讲完了,
sorry,因为我也只知道这么多而已啦~嘻~^_^
不过,还有一样东东是一定要讲的,各位观众(请自行配乐~!#@$%):
就是pipe line
也。
2.6 管道(pipe line)
谈到pipe line
,我相信不少人都不会陌生:
我们在很多command line上常看到|
符号就是pipe line了。
不过,pipe line究竟是什么东东呢?
别急别急…先查一下英文字典,看看pipe是什么意思?
没错他就是“水管”的意思…
那么,你能想象一下水管是怎样一个根接一根的吗?
又, 每根水管之间的input跟output又如何呢?
灵光一闪:原来pipe line的I/O跟水管的I/O是一模一样的:
上一个命令的stdout接到下一个命令的stdin去了
的确如此。不管在command line上使用了多少个pipe line,
前后两个command的I/O是彼此连接的
(恭喜:你终于开放了 ^_^ )
不过…然而…但是… …stderr呢?
好问题不过也容易理解:
若水管漏水怎么办?
也就是说:在pipe line之间,
前一个命令的stderr是不会接进下一个命令的stdin的,
其输出,若不用2>file的话,其输出在monitor上来。
这点请你在pipe line运用上务必要注意的。
那,或许你有会问:
有办法将stderr也喂进下一个命令的stdin吗?
(贪得无厌的家伙),方法当然是有的,而且,你早已学习过了。
提示一下就好:**请问你如何将stderr合并进stdout一同输出呢?
若你答不出来,下课后再来问我…(如果你脸皮足够厚的话…)
或许,你仍意犹未尽,或许,你曾经碰到过下面的问题:
在cmd1 | cmd2 | cmd3 | ...
这段pipe line中如何将cmd2的输出保存到一个文件呢?
若你写成cmd1 | cmd2 >file | cmd3
的话,
那你肯定会发现cmd3
的stdin是空的,(当然了,你都将
水管接到别的水池了)
聪明的你或许会如此解决:
是的,你可以这样做,但最大的坏处是:
file I/O会变双倍,在command执行的整个过程中,
file I/O是最常见的最大效能杀手。
凡是有经验的shell操作者,都会尽量避免或降低file I/O的频度。
那上面问题还有更好的方法吗?
有的,那就是tee
命令了。
所谓的tee
命令是在不影响原本I/O的情况下,
将stdout赋值到一个文件中去。
因此,上面的命令行,可以如此执行:
在预设上,tee
会改写目标文件,
若你要改为追加内容的话,那可用-a参数选项。
基本上,pipe line的应用在shell操作上是非常广泛的。
尤其是在text filtering方面,
如,cat, more, head, tail, wc, expand, tr,
grep, sed, awk…等等文字处理工具。
搭配起pipe line 来使用,你会觉得 command line
原来活得如此精彩的。
常让人有“众里寻他千百度,蓦然回首,那人却在灯火阑珊处”之感…
好了,关于I/O Redirection的介绍就到此告一段落。
若日后,有空的话,在为大家介绍其他在shell上好玩的东西。