介绍至此,我们对zsh的语法特点有了一个大概的了解,可以编写一些功能不复杂的脚本了。但是,shell脚本的主要应用场景并不是闭门写独立程序,而是与外部环境进行交互。所以要编写有用的脚本,你需要了解zsh如何与外部环境交互。这里的外部环境包括其他进程、文件系统、网络等。本文主要讲管道和重定向,它们是与其他进程、文件系统等进行交互的基础。本文中的命令主要是为了演示管道的使用,实际脚本中通常不需要使用这些命令,因为它们可以直接用zsh代码实现。另外,本系列文章不详细讲任何外部命令的用法,因为相关的文档或书籍实在是太多了。如果本文部分内容不理解,可以暂时跳过,基本不影响后面的理解。PipelinePipeline是类Unix系统中一个比较基础又特别重要的概念。它用于将一个程序的输出作为另一个程序的输入,使两个程序的数据能够进行通信。如果只是使用管道,非常简单易懂,不需要了解管道的实现细节。管道的基本用法:%lsgittmp#wc-l作用是计算输入内容的行数%ls|wc-l2|是管道,就是键盘上主键盘区右侧对应的shift键字符。如果只输入wc-l,wc会等待用户输入,这时可以输入一个字符串,然后回车继续输入,直到按ctrl+d结束输入。然后wc会统计用户一共输入了多少行,然后输出行数。#输入wc-l回车后,按a,回车b,回车ctrl+d%wc-lab2但是如果前面有管道符号,ls|wc-l,那么wc不会等待用户输入,而是直接读取ls的结果作为输入,统计行数,输出结果。有关管道的更多详细信息,让我们运行一个简单的示例:%cat|wc-l#查看cat进程打开的fd%ls-l/proc/$(pidofcat)/fdtotal0lrwx------1goreliugoreliu02017-08-3021:150->/dev/pts/1l-wx------1goreliugoreliu02017-08-3021:151->pipe:[2803]lrwx------1goreliugoreliu02017-08-3021:152->/dev/pts/1#查看wc进程打开的fd%ls-l/proc/$(pidofwc)/fdtotal0lr-x------1goreliugoreliu02017-08-3021:160->管道:[2803]lrwx------1goreliugoreliu02017-08-3021:161->/dev/pts/1lrwx------1goreliugoreliu02017-08-3021:162->/dev/pts/1cat命令的作用是等待用户输入,等待用户输入一行,就会输出这一行,直到用户按下ctrl-d.所以猫|wc-l还将等待用户输入。我们看一下fd的方向。/dev/ps1/1指向伪终端设备文件。该过程使用它来读取用户的输入并输出自己的内容。0为标准输入(即用户输入),1为标准输出(即正常情况下的输出),2为错误输出(即异常情况下的输出)。但是cat的输出指向一个管道,而wc的输入指向同一个管道,也就是说两个进程的输入输出是通过管道相连的。这种管道是匿名管道,即只存在于内核中,没有对应的文件路径。重定向是指fd的重定向,管道也是重定向的一种方法。但是更多的是用来将进程的fd重定向到一个文件中。最简单的示例之一是将内容输出到文件。%echoabce>test.txt%cattest.txtabce因为这个用法太普遍了,大家可能都习惯了。我们还是要看更多的细节。%cat>test.txt#在另一个zsh-l/proc/$(pidofcat)/fdtotal0lrwx------1goreliugoreliu0Aug3021:430->/dev/pts/1l中运行%ls-wx------1goreliugoreliu0Aug3021:431->/tmp/test.txtlrwx------1goreliugoreliu0Aug3021:432->/dev/pts/1可以看到标准输出已经指向了test.txt文件。除了标准输出可以重定向外,标准输入(fd0)、错误输出(fd2)也可以。%touch0.txt1.txt2.txt%sleep1000<0.txt>1.txt2>2.txt#在另一个zsh中运行%ls-l/proc/$(pidofsleep)/fdtotal0lr-x------1goreliugoreliu0Aug3021:460->/tmp/0.txtl-wx------1goreliugoreliu0Aug3021:461->/tmp/1。txtl-wx------1goreliugoreliu0Aug3021:462->/tmp/2.txt<0.txt是重定向标准输入,2>2.txt是重定向错误输出,>1.txt(即1>1.txt)被重定向到标准输出。然后我们看到3个文件都已就位,并且都已重定向。但由于sleep不读取或写入任何内容,因此重定向其输入和输出没有意义。重定向的更多用法一个fd只能重定向到一个文件,一一对应。但是在zsh中,我们可以将一个fd映射到多个文件。%cat>0.txt>1.txt>2.txt输入完成后,三个文件的内容全部更新。这是怎么回事?其实zsh进程就是中介。%pstree-p|grepcat`-tmux:server(1172)-+-zsh(1173)---cat(1307)---zsh(1308)%ls-l/proc/1307/fdtotal0lrwx------1goreliugoreliu0Aug3021:570->/dev/pts/1l-wx------1goreliugoreliu0Aug3021:571->pipe:[2975]lrwx------1goreliugoreliu0Aug3021:572->/dev/pts/1%ls-l/proc/1308/fdtotal0l-wx------1goreliugoreliu0Aug3021:5812->/tmp/0.txtl-wx------1goreliugoreliu0Aug3021:5813->/tmp/1.txtlr-x------1goreliugoreliu0Aug3021:5814->pipe:[2975]l-wx------1goreliugoreliu0Aug3021:5815->/tmp/2.txt可以看到cat的标准输出被重定向到了管道,Acrossthepipe是zsh进程,然后打开这三个文件。实际上将内容写入文件的是zsh,而不是cat。但是不管是谁写的,这个用法都很方便。标准输入和错误输出也可以重定向到多个文件。%echogood>0.txt>1.txt>2.txt%cat<0.txt<1.txt<2.txtgoodgoodgood将cat的标准输出重定向到3个文件,它会读取这3个文件的所有内容出去。fd除了可以同时重定向到多个文件,还可以同时重定向到管道和文件。#输入abc后,ctrl-d退出%cat>0.txt>1.txt|wc-labc3%cat0.txt1.txtabcabc可以看到输入的内容写入文件,通过管道-l传递给wc,不用说,这又是zsh在做幕后工作,分发数据给文件和管道。所以不需要在zsh中使用tee命令。命名管道除了匿名管道,我们还可以使用命名管道,这样更容易控制。命名管道使用的文件是FIFO(先输入先输出)文件。#mkfifo用于创建FIFO文件%mkfifofifo%ls-lprw-r--r--1goreliugoreliu02017-08-3021:29fifo|#cat写入fifo%cat>fifo#打开另一个zsh,运行wc-l读取fifo%wc-l?fifo然后在cat中输入一些内容,按ctrl-d退出,wc会统计输入的行数。在输入完成之前,我们还可以看看cat和wc进程的fd指向哪里:%ls-l/proc/$(pidofcat)/fdtotal0lrwx------1goreliugoreliu0Aug3021:350->/dev/pts/2l-wx------1goreliugoreliu08月30日21:351->/tmp/fifolrwx------1goreliugoreliu08月30日21:352->/dev/pts/2%ls-l/proc/$(pidofwc)/fdtotal0lr-x------1goreliugoreliu08月30日21:340->/tmp/fifolrwx------1goreliugoreliu0Aug3021:341->/dev/pts/1lrwx------1goreliugoreliu0Aug3021:342->/dev/pts/1可以看到前面的匿名管道变成了我们刚刚创建的fifo文件,其他没有什么不同。exec命令的使用说到重定向,就不得不提到exec命令。exec命令主要用来启动一个新的进程来代替当前进程,对fd做一些操作。使用exec启动一个新进程:%execcat看起来像直接运行cat。但是如果你运行ctrl+d退出cat,终端模拟器会关闭,因为运行execcat时,zsh进程已经被cat替换,无法返回。但是exec在脚本中很少直接使用,更多时候是用来操作fd:#将当前zsh的错误输出重定向到test.txt%exec2>test.txt#随机输入一个不存在的命令,不出现错误提示%fdsafds#错误提示重定向到test.txt%cattest.txtzsh:commandnotfound:fdsafds更多用法:用法函数n>filename将fdn的输出重定向到filename文件n
