> 与 < 差在哪?-- Shell十三问<第十一问>

谈到 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): keyboard
  • stdout(1): monitor
  • stderr(2): monitor

我们可以用如下下命令测试一下:

$ mail -s test root
this is a test mail.
please skip.
^d (同时按 crtl 跟 d 键)

很明显,mail 程序所读进的数据,就是从 stdin 也就是 keyboard 读进的。不过,不见得每个程序的 stdin 都跟 mail 一样从 keyboard 读进,因为程序作者可以从档案参数读进 stdin ,如:

$ cat /etc/passwd

但,要是 cat 之后没有档案参数则又如何呢?哦,请您自己玩玩看啰..

$ cat

(请留意数据输出到哪里去了,最后别忘了按 ^d 离开...)

相信,经过上一个练习后,你对 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 no.such.file
ls: no.such.file: No such file or directory
my.file
至此,关于 FD 及其名称、还有相关联的设备,相信你已经没问题了吧?

那好,接下来让我们看看如何改变这些 FD 的预设数据信道,

我们可用 < 来改变读进的数据信道(stdin),使之从指定的档案读进。

我们可用 > 来改变送出的数据信道(stdout, stderr),使之输出到指定的档案。

比方说:

$ cat < my.file
就是从 my.file 读进数据 $ mail -s test root < /etc/passwd
则是从 /etc/passwd 读进...
这样一来,stdin 将不再是从 keyboard 读进,而是从档案读进了.严格来说,< 符号之前需要指定一个 FD 的(之间不能有空白),
但因为 0 是 < 的默认值,因此 < 与 0< 是一样的!这个好理解吧?那,要是用两个 << 又是啥呢?
这是所谓的 HERE Document ,它可以让我们输入一段文本,直到读到 << 后指定的字符串。
比方说:
$ cat <<FINISH
first line here
second line there
third line nowhere
FINISH 这样的话,cat 会读进 3 行句子,而无需从 keyboard 读进数据且要等 ^d 结束输入。
至于 > 又如何呢?
当你搞懂了 0< 原来就是改变 stdin 的数据输入信道之后,相信要理解如下两个
redirection 就不难了:
* 1>
* 2>
前者是改变 stdout 的数据输出信道,后者是改变 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 的前面 6 个字符就被 stderr 覆盖掉了。
那,如何解决呢?所谓山不转路转、路不转人转嘛,我们可以换一个思维:将 stderr 导进 stdout 或将 stdout 导进 sterr ,而不是大家在抢同一份档案,不就行了! bingo﹗就是这样啦:
* 2>&1 就是将 stderr 并进 stdout 作输出
* 1>&2 或 >&2 就是将 stdout 并进 stderr 作输出
于是,前面的错误操作可以改为: $ ls my.file no.such.file 1>file.both 2>&1

$ ls my.file no.such.file 2>file.both >&2

这样,不就皆大欢喜了吗? 呵!

不过,光解决了同时写入的问题还不够,我们还有其它技巧需要了解的。

故事还没结束,别走开!广告后,我们再回来...!

在 Linux 档案系统里,有个设备档位于 /dev/null 。

许多人都问过我那是甚么玩意儿?我跟你说好了:那就是"空"啦!

没错!空空如也的空就是 null 了.... 请问施主是否忽然有所顿误了呢?然则恭喜了.

这个 null 在 I/O Redirection 中可有用得很呢:

  • 若将 FD1 跟 FD2 转到 /dev/null 去,就可将 stdout 与 stderr 弄不见掉。
  • 若将 FD0 接到 /dev/null 来,那就是读进 nothing 。

    比方说,当我们在执行一个程序时,画面会同时送出 stdout 跟 stderr ,假如你不想看到 stderr (也不想存到档案去),那可以:
$ ls my.file no.such.file 2>/dev/null
my.file
若要相反:只想看到 stderr 呢?还不简单!将 stdout 弄到 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
(提示:将 &> 换成 >& 也行啦~~! )
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

如此一来,被重导的目标档案之内容并不会失去,而新的内容则一直增加在最后面去。但,只要你再一次用回单一的 > 来重导的话,那么,旧的内容还是会被"洗"掉的!

这时,你要如何避免呢?

----备份! 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
留意到没有:在 > 后面再加个" | "就好(注意: > 与 | 之间不能有空白哦)....

再来还有一个难题要你去参透的呢:

$ 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
嗯?!注意到没有?!!
---- 怎么最后那个 cat 命令看到的 file 竟是空的?!
why? why? why?

前面提到:$ cat < file > file 之后原本有内容的档案结果却被洗掉了!

要理解这一现像其实不难,这只是 priority 的问题而已:

  • 在 IO Redirection 中,stdout 与 stderr 的管道会先准备好,才会从 stdin 读进资料。也就是说,在上例中,> file 会先将 file 清空,然后才读进 < file , 但这时候档案已经被清空了,因此就变成读不进任何数据了...

    哦~ 原来如此~!

    那. 如下两例又如何呢?
$ cat <> file
$ cat < file >> file

嗯. 同学们,这两个答案就当练习题啰,下节课之前请交作业!

好了,I/O Redirection 也快讲完了,sorry,因为我也只知道这么多而已啦~~~ 嘻~~ 不过,还有一样东东是一定要讲的,各位观众(请自行配乐~!#@!$%) :就是 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 都是彼此连接的!(恭喜:你终于开窍了! )

    若水管漏水怎么办?

    也就是说:在 pipe line 之间,前一个命令的 stderr 是不会接进下一命令的 stdin 的,

    其输出,若不用 2> 导到 file 去的话,它还是送到监视器上面来!

    这点请你在 pipe line 运用上务必要注意的。

    那,或许你又会问:
  • 有办法将 stderr 也喂进下一个命令的 stdin 去吗?

    方法当然是有,而且你早已学过了!

我提示一下就好:

  • 请问你如何将 stderr 合并进 stdout 一同输出呢?

或许,你仍意尤未尽!或许,你曾经碰到过下面的问题:

  • 在 cm1 | cm2 | cm3 ... 这段 pipe line 中,若要将 cm2 的结果存到某一档案呢?

    若你写成 cm1 | cm2 > file | cm3 的话,

    那你肯定会发现 cm3 的 stdin 是空的!(当然啦,你都将水管接到别的水池了!)聪明的你或许会如此解决:
cm1 | cm2 > file ; cm3 < file

是的,你的确可以这样做,但最大的坏处是:这样一来,file I/O 会变双倍!在 command 执行的整个过程中,file I/O 是最常见的最大效能杀手。凡是有经验的 shell 操作者,都会尽量避免或降低 file I/O 的频率。那,上面问题还有更好方法吗?

有的,那就是 tee 命令了。

  • 所谓 tee 命令是在不影响原本 I/O 的情况下,将 stdout 复制一份到档案去。

    因此,上面的命令行可以如此:
cm1 | cm2 | tee file | cm3

在预设上,tee 会改写目标档案,若你要改为增加内容的话,那可用 -a 参数达成。基本上,pipe line 的应用在 shell 操作上是非常广泛的,尤其是在 text filtering 方面, 凡举 cat, more, head, tail, wc, expand, tr, grep, sed, awk, ... 等等文字处理工具, 搭配起 pipe line 来使用,你会惊觉 command line 原来是活得如此精彩的!

常让人有"众里寻他千百度,蓦然回首,那人却在灯火阑珊处!"之感.

最新文章

  1. ASP.NET Core 导入导出Excel xlsx 文件
  2. C语言与内存模型初探
  3. 2014 todo list
  4. windowSoftInputMode属性讲解
  5. equals方法的小结
  6. ubuntu&amp;FAQ
  7. Apple dev travel
  8. [ActionScript&amp;Flex] FlashBuilder编译条件之如何屏蔽调试代码
  9. Android使用 LruCache 缓存图片
  10. Web桌面应用框架3:Web桌面应用开发的N种Style
  11. 【转】.MD语法入门
  12. u-boot(五)内核启动
  13. C#与 微信小程序 互为加解密方案
  14. VS Code打开使用IDEA搭建的Spring Boot项目运行提示&quot;snakeyaml was not found on the classpath&quot;错误
  15. JS数组的需要注意的问题
  16. js中如何将数据获得2位小数以及对数据进行千分位划分
  17. Windows下搭建elasticsearch集群案例
  18. ubuntu16.04系统gcc下降和升级
  19. 教程 | 一文入门Python数据分析库Pandas
  20. 13.Github使用

热门文章

  1. React In Depth
  2. js 最简单的发布订阅模式
  3. NGK内存爆发式增长,看Baccarat将怎样打造全新的全场景金融生态
  4. halo博客安装教程,一款优秀的java开源博客系统
  5. 1.代码规范之 if 语句编写
  6. CentOS7安装Kafka2.6.0
  7. C++ Primer Plus 第一章 预备知识
  8. go 在crontab里面运行报错 解决方案
  9. C++类的友元机制说明
  10. HDOJ-1069(动态规划+排序+嵌套矩形问题)