这次的题目,之前我在 CU 的 shell 版说明过了: (原帖的连接在论坛改版后,已经失效) 这次我就不重写了,将帖子的内容 “抄” 下来就是了...
文件描述符 (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 -s test root
this is a test mail。
please skip.
^d (同时按下ctrl 跟d键)
很明显,mail
进程所读进的数据,就是从 stdin
也就是 keyboard 读进的。 不过,不见得每个进程的stdin
都跟mail
一样 从keyboard
读进,因为进程的作者可以从文件参数读进stdin
, 如:
$ cat /etc/passwd
但,要是cat
之后没有文件参数则如何呢? 哦, 请你自己玩玩看...^_^
$ cat
Tips:
请留意数据输出到哪里去了, 最后别忘了按
ctrl+d(^d)
, 退出 stdin 输入。
至于stdout
与stderr
,嗯... 等我有空再续吧...^_^ 还是,有哪位前辈来玩接龙呢?
相信,经过上一个练习后, 你对stdin
与stdout
应该不难理解了吧? 然后,让我们看看stderr
好了。
事实上,stderr
没什么难理解的: 说白了就是 “错误信息” 要往哪里输出而已... 比方说, 若读进的文件参数不存在的, 那我们在 monitor 上就看到了:
$ ls no.such.file
ls: no.such.file: No such file or directory
若同一个命令,同时成生stdout
与stderr
呢? 那还不简单,都送到 monitor 来就好了:
$ touch my.file
$ ls my.file on.such.file
ls: no.such.file: No such file or directory
my.file
okay, 至此,关于 fd 及其名称、还有相关联的设备, 相信你已经没问题了吧?
I/O 重定向 (I/O Redirection)
那好,接下来让我们看看如何改变这些 fd 的预设数据通道。
- 用
<
来改变读进的数据通道 (stdin),使之从指定的文件读进。 - 用
>
来改变输出的数据通道 (stdout,stderr), 使之输出到指定的文件。
输入重定向n<(input redirection)
比方说:
$ cat < my.file
就是从 my.file 读入数据
$ mail -s test root < /etc/passwd
则是从 / etc/passwd 读入...
这样一来,stdin 将不再是从 keyboard 读入, 而是从指定的文件读入了...
严格来说,<
符号之前需要指定一个 fd 的 (之前不能有空白),但因为 0 是<
的预设值,因此,<
与0<
是一样的 。
okay,这样好理解了吧?
那要是用两个<
,即<<
又是啥呢? 这是所谓的here document
,它可以让我们输入一段文本,直到读到<< 后指定的字符串。
比方说:
$ cat <<EOF
first line here
second line here
third line here
EOF
这样的话,cat
会读入 3 个句子, 而无需从 keyboard 读进数据且要等到 (ctrl+d, ^d) 结束输入。
重定向输出>n(output redirection)
当你搞懂了0<
原来就是改变stdin
的数据输入通道之后, 相信要理解如下两个 redirection 就不难了:
1>
#改变 stdout 的输出通道;2>
#改变 stderr 的输出通道;
两者都是将原来输出到 monitor 的数据, 重定向输出到指定的文件了。
由于 1 是>
的预设值, 因此,1>
与>
是相同的,都是改变stdout。
用上次的 ls 的例子说明一下好了:
$ ls my.file no.such.file 1>file.out
ls: no.such.file: No such file or directory
这样 monitor 的输出就只剩下stderr的输出了, 因为stdout重定向输出到文件 file.out 去了。
$ ls my.file no.such.file 2>file.err
my.file
这样 monitor 就只剩下了stdout, 因为stderr重定向输出到文件 file.err 了。
$ ls my.file no.such.file 1>file.out 2>file.err
这样 monitor 就啥也没有了, 因为stdout
与stderr
都重定向输出到文件了。
呵呵,看来要理解>
一点也不难啦是不? 没骗你吧? ^_^ 不过有些地方还是要注意一下的。
$ ls my.file no.such.file 1>file.both 2>file.both
假如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
输出。
这样,不就皆大欢喜了吗? ~~~ ^_^
不过,光解决了同时写入的问题还不够, 我们还有其他技巧需要了解的。 故事还没有结束,别走开广告后,我们在回来....
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(也不想存到文件), 那就可以:
$ ls my.file no.such.file 2>/dev/null
my.file
若要相反:只想看到 stderr 呢? 还不简单将 stdout,重定向的 / dev/null 就行:
$ ls my.file no.such.file >/dev/null
ls: no.such.file: No such file or directory
那接下来,假如单纯的只跑进程,而不想看到任何输出呢? 哦,这里留了一手,上次没讲的法子, 专门赠与有缘人... ^_^ 除了用 >/dev/null 2>&1
之外,你还可以如此:
$ ls my.file no.such.file &>/dev/null```
>**Tips:**
>
>将 &> 换成 >& 也行!
### 重定向输出 append (>>)
okay? 请完佛,接下来,再让我们看看如下情况:
$ echo "1" > file.out $ cat file.out 1 $ echo "2" > file.out $ cat file.out 2
看来,我们在重定向 stdout 或 stderr 进一个文件时, 似乎永远只能获得最后一次的重定向的结果. 那之前的内容呢?
呵呵,要解决这个问题,很简单啦,将`>`换成`>>` 就好了;
$ echo "3" >> file.out $ cat file.out 2 3
如此一来,被重定向的文件的之前的内容并不会丢失, 而新的内容则一直追加在最后面去。so easy?...
但是,只要你再次使用`>`来重定向输出的话, 那么,原来文件的内容被 truncated(清洗掉)。 这是,你要如何避免呢? ---- 备份, yes,我听到了,不过,还有更好的吗? 既然与施主这么有缘分,老衲就送你一个锦囊妙法吧:
$ set -o noclobber $ echo "4" > file.out -bash:file: cannot overwrite existing file.
那,要如何取消这个限制呢? 哦,将`set -o`换成 `set +o`就行了:
$ set +o noclobber $ echo "5" > file.out $ cat file.out 5
再问:那有办法不取消而又 “临时” 改写目标文件吗? 哦,佛曰:不可告也。 啊,~ 开玩笑的,开玩笑啦~^_^, 哎,早就料到人心是不足的了
$ set -o noclobber $ echo "6" >| file.out $ cat file.out 6
留意到没有: **在`>`后面加个`|`就好, 注意: `>`与`|`之间不能有空白哦**...
### I/O Redirection 的优先级
呼....(深呼吸吐纳一下吧)~~~ ^_^ 再来还有一个难题要你去参透呢:
$ echo "some text here" >file $ cat < file some text here $cat < file >file.bak $cat < file.bak some text here $cat < file >file
嗯?注意到没有? --- 怎么最后那个 cat 命令看到 file 是空的呢? why? why? why?
前面提到:`$cat < file > file`之后, 原本有内容的文件,结果却被清空了。 要理解这个现象其实不难, 这只是 priority 的问题而已: ** 在 IO Redirection 中, stdout 与 stderr 的管道先准备好, 才会从 stdin 读入数据。** 也就是说,在上例中,`>file`会将 file 清空, 然后才读入 `< file`。 但这时候文件的内容已被清空了,因此就变成了读不进任何数据。
哦,~ 原来如此~^_^ 那... 如下两例又如何呢?
$ cat <> file $ cat < file >>file