尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Shell编程实例——标准输出

Shell编程实例——标准输出 标准输出1、输出到终端/终端窗口2、保留输出中的空白字符3、在输出中加入更多格式控制3、消除输出中的换行符5、保存命令输出6、将输出保存到其他文件7、保存ls命令的输出8、将输出和错误消息发送到不同文件9、将输出和错误消息发送到同一文件10、追加输出11、仅使用文件的起始或结尾部分12、跳过文件标题13、丢弃输出14、保存或分组多个命令的输出15、将输出作为输入连接两个程序16、将输出作为输入同时保留其副本17、以输出为参数连接两个程序18、在一行中多次重定向19、重定向不起作用时保存输出20、交换STDERR和STDOUT21、避免意外覆盖文件22、有意覆盖文件1、输出到终端/终端窗口使用内建命令 echo。命令行中的所有参数都会打印到屏幕上。例如echoPlease wait.输出Please wait.结果和在 bash 提示符字符 $后输入该命令相同$echoPlease wait. Please wait. $echo 是最简单的 bash 命令之一。该命令可以将参数输出到屏幕上。但是有几点需要记住。首先shell 负责解析echo 的命令行参数shell 对其他命令也是如此​。这意味着在将参数交给 echo 前shell 会完成所有的替换、通配符匹配等操作。其次在解析参数时参数之间的空白字符会被忽略。例如$echothis was very widely spaced this was very widely spaced $shell 对参数间的空白字符没有太多限制这通常是一种不错的特性。但对于 echo 来说就有点烦人了2、保留输出中的空白字符将字符放入引号中。在上一节的示例中加入引号就可以保留空白字符$echothis was very widely spacedthis was very widely spaced $或者$echothis was very widely spacedthis was very widely spaced $引号中的单词组成了 echo 命令的单个参数。该参数是一个字符串shell 不会干涉字符串的内容。实际上可以用单引号‘’明确告诉 shell 不要干涉字符串。如果使用的是双引号“”​那么 shell 还是会执行一些替换操作变量扩展、算术扩展、波浪号扩展以及命令替换​但上述示例不涉及这些操作所以输出没有什么变化。如果不确定那就用单引号。3、在输出中加入更多格式控制2.3.2 解决方案$printf%s %d\nLines$LINESLines24$或者$printf%-10.10s %4.2f\nGigahertz1.92735Gigahertz1.93$内建命令 printf 的行为和 C 语言中的同名库函数相似其中第一个参数是格式控制字符串之后的参数都根据格式规范%进行格式化。% 和格式类型本例为 s 或 f之间的数字提供了额外的格式化细节。对于浮点类型f​第一个数字指示符4.2 中的 4是整个字段的宽度。第二个数字2是应该在小数点右侧打印出的数位量。注意结果会按照四舍五入处理。对于字符串第一个数字是字段的最大宽度第二个数字是要输出的字符数量。根据需要字符串会被截断长于max或用空白填充不足 min​。如果指示符 max 和min 相同那么就可以确保字符串按照该长度输出。指示符左侧的负号表示字符串向左对齐在字段宽度内​。如果不使用负号则字符串向右对齐因此$printf%10.10s %4.2f\nGigahertz1.92735Gigahertz1.93$可以引用字符串参数也可以不引用。如果需要保留字符串中嵌入的空白字符本例的字符串中只有一个单词不存在空格​或者需要转义字符串中的某些特殊字符本例也未出现这些特殊字符​可以加上引号 1。最好养成将传给 printf 的字符串都放进引号中的习惯这样一来需要用到引号时就不会遗漏了。3、消除输出中的换行符你希望输出中不包含 echo 默认生成的换行符。使用 printf做法很简单去掉格式化字符串末尾的 \n 即可。$printf%s %snext prompt next prompt$如果是 echo则使用 -n 选项$echo-nprompt prompt$因为 printf 的格式字符串第一个参数末尾并没有换行符所以命令行提示符$出现在了 printf 的输出之后。该特性在 shell 脚本中用处更大你可能希望在形成一整行前由多条语句逐部分输出或者在读取输入前显示用户提示。换作 echo 命令参见 15.6 节​消除换行符的方法有两种。首先-n 选项能够抑制输出行尾的换行符。另外echo 命令还可以处理多种具有特殊含义的转义序列如表示换行符的 \n​这些转移序列与 C 语言字符串中的类似。要想使用它们调用 echo 命令时必须加上 -e 选项。其中一种转义序列是 \C它并不会输出什么字符而是禁止在行尾输出换行符。因此下面是第三种解决方案$echo-ehi\chi$因为 printf 所提供的格式化功能既强大又灵活而且该命令还是内建的调用开销非常小不像在其他 shell 或老版 bash 中printf 是一个独立的可执行文件​所以本书中的很多示例都采用了 printf。5、保存命令输出你想把命令输出保存在文件中。用 符号告诉 shell 将输出重定向至文件。例如$echofill it up fill it up $echofill it upfile.txt $我们来查看一下文件 file.txt 的内容看看其中是否包含了命令的输出$catfile.txt fill it up $示例第一部分的第一行中出现的 echo 命令包含了 3 个要输出的参数。第二行用 将这些输出保存到文件 file.txt中这就是看不到 echo 输出的原因。示例第二部分用 cat 命令显示文件内容。我们可以看出文件中包含的正是 echo 本该输出的内容。cat 命令得名自一个较长的单词 concatenation拼 接​。该命令会将出现在命令行上的文件的输出拼接在一起如果你输入 cat file1 filetwo anotherfile morefiles那么这些文件的内容会逐个发送到终端窗口。如果一个大文件被分成了两半你也可以用 cat 将其恢复原样也就是将两部分拼接起来​这只需将输出保存到另一个文件中catfirst.half second.halfwhole.file所以说前面那个简单的命令 cat file.txt 所做的事其实不过是拼接出了一个文件并将结果显示在屏幕上。尽管 cat的能耐不小但在本例中的主要用途就是在屏幕上显示文件内容。6、将输出保存到其他文件你想要用重定向将输出保存到当前目录之外的其他位置。重定向输出时加上路径echosomemoredata/tmp/echo.out或者echosomemoredata../../over.here出现在重定向符号后的文件名其实就是路径名。如果没有任何限定部分那么文件就会放置在当前目录中。如果文件名以斜线/起始那就是绝对路径名此时文件会被放置在文件系统层次结构目录树中以根目录起始的指定位置假设所有的中间目录都存在且你有权限进入​。我们使用了 /tmp这是一个几乎所有 Unix 系统都存在且普遍可用的临时目录。在本例中shell 会在/tmp 目录中创建名为 echo.out 的文件。在第二个例子中我们使用了相对路径名…/…/over.here其中的 … 是一个指向父目录的特殊目录存在于每个目录中。因此每次引用 … 就会在文件系统目录树中向上往根的方向并非惯常意义中的沿着树“向上”​移动一级。这里的重点是我们可以按照自己的意愿将输出重定向到当前目录之外的某个文件中。7、保存ls命令的输出你尝试用重定向保存 ls 命令的输出但查看结果文件时发现格式和预想的不同。重定向输出时使用 ls 命令的 -C 选项。下面用 ls 命令显示目录内容$lsa.out cong.txt def.conf file.txt more.txt zebra.list $但是当用 将输出重定向到文件并显示该文件内容时看到的是如下结果$ls/tmp/save.out $cat/tmp/save.out a.out cong.txt def.conf file.txt more.txt zebra.list $这次我们加上 -C 选项$ls-C/tmp/save.out $cat/tmp/save.out a.out cong.txt def.conf file.txt more.txt zebra.list $不进行重定向时也可以使用 ls 的 -1 选项得到的输出如下所示$ls-1a.out Cong.txt def.conf file.txt more.txt zebra.list $这和最初使用重定向时的输出结果一样。当你觉得自己已经理解重定向试着将它用于简单的 ls 命令时结果却不尽正确。这是怎么回事shell 的重定向功能意在对所有程序保持透明因此程序无须编写特定代码来让自身的输出能够被重定向。当你用 将输出发送到别处时shell 负责完成相关工作。但是我们可以在程序中加入代码判断输出何时被发往终端参见 man isatty​。然后程序就能够针对不同情况进行处理这也正是 ls 所做的。ls 的作者认为如果要向屏幕输出那么用户可能希望按列输出-C 选项​毕竟屏幕空间有限寸土寸金。但作者同时也认为如果重定向到文件那么用户会希望一行显示一个文件名-1 选项​这样一来更容易实现一些有意思的事情其他处理​。8、将输出和错误消息发送到不同文件你希望获得程序的输出但不想输出被出现的错误消息弄乱。你要保存的错误消息混杂在程序输出中不容易找出。将输出和错误消息重定向到不同文件myprogram1messages.out2message.err或者采用更常见的方法myprogrammessages.out2message.err这个示例展示了 shell 创建的两个输出文件。第一个是messages.out假想程序 myprogram 的所有输出都会重定向到该文件。myprogram 的所有错误消息都会重定向到 message.err。在 1 和 2 中数字表示文件 描述符。1 代表标准输出STDOUT​2 代表标准错误STDERR​。起始编号 0对应标准输入STDIN​。如果不指定数字则假定为STDOUT。9、将输出和错误消息发送到同一文件利用重定向我们可以将输出或错误消息保存到单独的文件中但如何将两者送往同一文件呢用 shell 语法将标准错误消息重定向到和标准输出相同的地方。首选bothoutfile或者bothoutfile又或者老式且略烦琐但可移植性更好的写法bothoutfile21其中both 是准备向 STDERR 和 STDOUT 生成输出的假想程序。 和 只是将 STDOUT 和 STDERR 发送到相同地方这正是我们想做的的便捷写法。在第三个示例中1 用作重定向的目标但是 将 1 解释为文件描述符。实际上21 是一个实体其中不允许出现空格​表示标准错误2会被重定向到随后的文件描述符1。2 必须作为整体出现不能夹杂空格否则2 就成了另一个参数而 代表与其表面完全不同的含义与在后台运行命令有关​。不妨认为所有的重定向操作符都带有一个前导数字如2​而 标准输出的文件描述符的默认数字为 1。尽管可读性会略差你也可以按照其他顺序进行重定向将标准输出的指向重定向到与标准错误相同的地方实际上使用管道就必须这么做​both2outfile121 代表标准输出2 代表标准错误。我们本可以将最后一个重定向写作 2因为对于 而言1 是默认的但是我们发现重定向文件描述符时明明白白地写出数字可读性会更好。注意输出文件的内容顺序。有时候错误消息在文件中出现的时间可能早于在屏幕上出现的时间。这与标准错误的无缓冲性质有关当写入文件而不是屏幕时这种现象会更加明显。10、追加输出每次重定向输出都会产生一个全新的输出文件。如果想要两次或三次、四次……重定向输出同时又不想破坏之前的输出该怎么办呢在 bash 的重定向符号中双大于号表示追加输出$ls/tmp/ls.out $cd../elsewhere $ls/tmp/ls.out $cd../anotherdir $ls/tmp/ls.out $如果存在同名文件第一行中的重定向会将其截断并将ls 命令的输出保存在这个已被清空的文件中。后两次调用 ls 时使用了双大于号​表示向输出文件中追加内容而不是覆盖其原有内容。如果想要同时重定向错误消息STDERR​可以将STDERR 的重定向放在后面如下所示ls/tmp/ls.out21在 bash 4 中你可以将这两个重定向合二为一ls/tmp/ls.out该命令会重定向 STDERR 和 STDOUT并将两者追加到指定文件中。记住 符号必须先出现且这 3 个字符之间不能有空格。11、仅使用文件的起始或结尾部分你只需要显示或使用文件的起始或结尾部分。使用 head 或 tail 命令。默认情况下head 输出指定文件的前 10 行tail 输出最后 10 行。如果指定多个文件则依次输出各文件中相应的行。可以通过 -number 选项如 -5修改默认行数。tail 还有 -f 和 -F 选项这两个选项能够跟踪文件末尾的写入下面还会介绍另一个有意思的加号选项。head、tail、cat、grep、sort、cut 和 uniq 都属于最常用的 Unix 文本处理工具。如果尚不熟悉这些工具的用法那么你很快就会好奇自己是如何在没有它们的情况下撑过来的。12、跳过文件标题文件中有标题行你希望将其跳过只处理数据。使用带有特殊选项的 tail 命令。例如跳过文件的第一行$tail-n2 lines Line2Line3Line4Line5$tail 命令的选项 -n number或者 -number可以指定相对于文件末尾的行偏移。因此tail -n 10 file 会显示 file的最后 10 行这也是不指定任何选项时的默认处理方式。如果数字以加号开头则表示相对于文件起始的行偏移。tain -n 1 file 会显示整个文件tain -n 2 file则是跳过第一行剩下的以此类推。13、丢弃输出你有时不想将输出保存到文件中实际上有时甚至不想看到输出。将输出重定向到 /dev/null如下所示find/-namemyfile-print2/dev/null或者noisy/dev/null21你可以将不想要的输出重定向到文件然后再将其删除。但还有一个更简单的方法。Unix 和 Linux 系统都存在一个特殊设备该设备并非真实的硬件而仅仅是一个位桶bit bucket​我们可以将不需要的数据都扔进去。它就是 /dev/null非常适用于此类场景。写入其中的数据会被直接丢弃不会占用磁盘空间。重定向很容易做到这一点。在第一个示例中只有发往标准错误的输出被丢弃了。在第二个示例中标准输出和标准错误均被丢弃。在极少数情况下/dev 可能会位于只读文件系统如某些信息安全装置中此时你可以采用第一种方法将输出重定向到文件然后再删除该文件。14、保存或分组多个命令的输出你想用重定向获得输出但是在一行中输入了多个命令pwd;ls;cd../elsewhere;pwd;ls/tmp/all.out末尾的重定向仅应用于最后那个 ls 命令。其他命令的输出依旧出现在屏幕上并未被重定向​。使用花括号{}将这些命令组合在一起然后将重定向应用于分组中所有命令的输出。例如{pwd;ls;cd../elsewhere;pwd;ls;}/tmp/all.out这里有两个不易察觉的细微之处。花括号实际上是保留字因此两侧必须有空白字符。另外闭合花括号之前的拖尾分号也是不能少的。或者你也可以用括号()告诉 bash 在子 shell 中运行这些命令然后重定向整个子 shell 的输出。例如(pwd;ls;cd../elsewhere;pwd;ls)/tmp/all.out尽管这两种解决方案看起来差不多但存在两处重要的区别。第一处是语法上的第二处是语义上的。从语法上来看花括号两侧需要有空白字符命令列表中的最后一个命令必须以分号结尾。如果使用括号那就不要求这些了。更大的区别在于语义方面也就是这些构件所代表的含义。花括号只是一种组合多个命令的方式而已更像是重定向的便捷写法这样我们就不用单独重定向各个命令了。而出现在括号中的命令是在 shell 的另一个实例中运行也就是当前 shell 的子 shell。子 shell 几乎复刻了当前 shell 的环境包括 $PATH 在内的变量都是一模一样的但对陷阱的处理有所不同​。采用子 shell 方法的重大不同在于因为 cd 命令是在子 shell 中执行的所以退出子 shell 后父 shell 的当前目录仍保持原样shell 变量也不会发生变化。如果使用花括号分组命令最后你会位于一个全新的目录中这个示例为 …/elsewhere​。你所做的其他改动如变量赋值也会应用于当前 shell。虽然两种方法会获得相同的输出但最终所在的目录位置大不相同。花括号有一种值得注意的用法即能够形成更简洁的分支语句块​。你可以缩短下列语句if[$result1];thenechoResult is 1; excellent.exit0elseechoUh-oh, ummm, RUN AWAY! exit120fi精简成[$result1]\{echoResult is 1; excellent.;exit0;}\||{echoUh-oh, ummm, RUN AWAY! ;exit120;}采用哪种写法取决于你的个人风格以及对可读性的理解但我们推荐第一种形式这种写法更清晰适合更大范围的受众。15、将输出作为输入连接两个程序你希望从一个程序中获得输出再将其作为另一个程序的输入。可以将第一个程序的输出重定向到一个临时文件然后将该文件作为另一个程序的输入。例如$catone.file another.file/tmp/cat.out $sort/tmp/cat.out... $rm/tmp/cat.out $也可以将这些操作合为一步利用管道符号|将输出直接发送到下一个程序。例如catone.file another.file|sort还可以用多个管道将一系列命令连接在一起catmy*|tra-zA-Z|sort|uniq|awk-ftransform.awk|wc使用管道符号意味着不用再创建临时文件事后再将其删除。像 sort 这种程序既能从标准输入中获取输入通过 符号进行重定向​也能从作为参数的文件中获取输入。因此可以按以下方式操作sort/tmp/cat.out不用再将输入重定向到 sortsort/tmp/cat.out这种行为如果提供了文件名参数就使用文件作为输入否则使用标准输入是 Unix/Linux 的典型特征也是一种应该遵循的实用模型以便多个命令可以通过管道机制彼此相连。这种程序称为过滤器如果照此方式编写程序和 shell 脚本则更有助于你个人以及你的同事。尽情感叹简洁有力的管道机制吧。你甚至可以将管道视为一种初级的并行处理机制。你可以让两个命令程序并行运行并共享数据一个的输出作为另一个的输入。二者不必按顺序运行一个结束另一个接着开始​只要第一个命令产生可用数据第二个命令立刻就可以开始处理。但要注意按照这种方式运行的多个命令通过管道相连分别在多个独立的进程中运行。尽管这一细微之处经常被忽视有时所带来的影响却不容小视。16、将输出作为输入同时保留其副本你想要调试一个比较长的管道化 I/O 序列例如catmy*|tra-zA-Z|uniq|awk-ftransform.awk|wc在不中断管道的情况下该如何查看 uniq 和 awk 之间发生了什么解决方案是使用水管工在维修管线时会用到的 T 形接头。对于 bash 来说这意味着用 tee 命令将输出分成两个一模一样的流一个写入文件另一个写入标准输出以便继续沿着管道发送数据。对于上述示例我们在 uniq 和 awk 之间插入 tee 命令...uniq|tee/tmp/x.x|awk-ftransform.awk...tee 命令将输出写入其参数所指定的文件同时还将相同的输出写入标准输出。在上述示例中它将输出同时发送到 /tmp/x.x 和 awk后者通过管道与 tee 的输出连接在一起。不用关心示例中的这条命令的各个部分都是做什么的我们只是演示 tee 命令在命令序列中的用法。降低点儿难度来看一个更简单的命令行。假设你想要将某个长期运行的命令的输出保存起来以备随后参考同时还想在屏幕上看到这些输出。思考下面的命令find/-name*.c-print|less该命令会查找出大量的 C 程序源代码文件可能需要好几屏的输出。使用 more 或 less 可以一次只查看部分输出但命令结束后就无法回看之前的输出除非再次运行命令。当然你可以将输出保存到文件find/-name*.c-print/tmp/all.my.sources但这样的话只有等到命令结束后才能看到文件内容。​好了我们知道还有 tail -f但现在说这个就跑题了。​tee 命令可以用来代替重定向标准输出的做法find/-name*.c-print|tee/tmp/all.my.sources在这个例子中因为 tee 的输出并未重定向到别处因此会显示在屏幕上。但是输出的副本也会发送给指定的文件如 cat /tmp/all.my.sources​以备后用。还要注意在这几个例子中我们压根没有重定向过标准错误。这意味着错误如来自 find 命令会显示在屏幕上但不会出现在 tee 指定的文件中。我们可以在 find 命令中加入 21find/-name*.c-print21|tee/tmp/all.my.sources这样就可以将错误输出一并存入 tee 指定的文件。错误输出与普通输出之间未必泾渭分明但的确能被捕获到。17、以输出为参数连接两个程序如果想要接入管道的程序并不适用于管道该怎么办例如你可以用 rm 命令删除文件将待删除的文件指定为命令参数即可rmmy.java your.c their.*但是rm 并不会从标准输入中读取参数因此不能按以下方式这么做find.-name*.c|rmrm 只能以命令行参数的形式获取文件名那该如何将先前运行过的命令如 echo 或 ls的输出放入命令行呢使用 bash 的命令替换特性rm$(find.-name*.class)也可以使用 xargs 命令。出现在( ) 中的命令是在子 s h e l l 中运行的。 () 中的命令是在子 shell 中运行的。()中的命令是在子shell中运行的。() 会被替换成所包含命令的输出。换行符会将输出变成多个命令行参数 尽管这种行为往往很有用但有时也会产生出人意料的结果。早期的 shell 语法没有使用( ) 而是将命令放进反引号 ‘ ‘ ​。在语法上 ()而是将命令放进反引号​。在语法上()而是将命令放进反引号‘‘​。在语法上() 优于老式的因为前者更利于嵌套自然也更容易阅读。但你可能更多看到的是尤其是在有点年代的 shell 脚本中或是从早先的 Bourneshell/C shell 成长起来的人们那里。在这个例子中find 命令的输出通常是一系列文件名成了 rm 命令的参数。做这种操作时要格外小心因为 rm 可是不讲情面的。如果 find 找到的文件比预想的多rm也会将其一并删除无可挽回。这可不是 Windows你无法从回收站中恢复已删除文件。可以用 rm -i 降低风险选项 -i 会提醒你确认每次删除。对于小批量文件来说这么做没毛病但如果文件数量众多那整个过程可就冗长不堪了。在 bash 中更安全地使用该机制的一种方法是先运行其中的命令看看结果是否符合预期如果没问题再将该命令放入 $()。例如$find.-name*.classFirst.class Other.class $rm$(find.-name*.class)$18、在一行中多次重定向你想将输出重定向到多个地方。用带有文件描述符编号的重定向打开所有要使用的文件。例如divert3file.three4file.four5file.five6else.where其中divert 可能是包含各种命令的 shell 脚本你希望将其输出发送到不同地方。例如divert 可能包含 echooption $OPTSTR 5。也就是说shell 脚本 divert 可以将其输出定向到不同的描述符调用程序invokingprogram可以将描述符指向不同的目标。与此类似如果 divert 是一个 C 程序的可执行文件则无须任何 open() 调用就可以向文件描述符 3、4、5、6 写入。前面介绍过每个文件描述符都由一个数字从 0 开始代表标准输入是 0标准输出是 1标准错误是 2。如果不指定数字则假定为 1。这意味着可以用略显啰唆 的写法 1而不是简单的 跟上文件名来重定向标准输出不过其实没这个必要便捷写法 就挺好。可以在shell 中打开任意数量的文件描述符令其指向各种文件这样一来随后在命令行上调用的程序就不用再费事了直接就可以使用这些已打开的文件描述符。我们不建议这么做因为这种技术不仅脆弱而且具有不必要的复杂性不过倒是挺有意思的。19、重定向不起作用时保存输出你使用了 但是有些或全部输出仍旧出现在屏幕上。例如编译器产生了下列错误消息$ gcc bad.c bad.c: Infunctionmain: bad.c:3: error:bad undeclared(first useinthisfunction)bad.c:3: error:(Each undeclared identifier is reported only once bad.c:3: error:foreachfunctionit appears in.)bad.c:3: error: parse error beforec$因为想要获取这些消息所以你尝试重定向输出$ gcc bad.csave.it bad.c: Infunctionmain: bad.c:3: error:bad undeclared(first useinthisfunction)bad.c:3: error:(Each undeclared identifier is reported only once bad.c:3: error:foreachfunctionit appears in.)bad.c:3: error: parse error beforec$但是看起来重定向好像没起什么作用。实际上如果检查重定向文件你会发现这个文件是空的长度为 0​$ls-lsave.it -rw-r--r--1albingusers02005-11-1315:30 save.it $catsave.it $重定向错误输出如下所示gcc bad.c2save.it现在save.it 的内容就是你先前看到的错误信息。这是怎么回事Unix 和 Linux 中的每个进程通常一开始都有 3 个已打开的文件描述符一个用于输入标准输入STDIN​一个用于输出标准输出 STDOUT​一个用于错误消息标准错误 STDERR​。至于是否遵循这种约定将错误消息写入标准错误将正常输出写入标准输出那真的就得看程序员了因此无法确保所有的错误消息都会进入标准错误。但大多数历史较久的实用工具很好地贯彻了这种做法。这就是为什么编译器消息无法使用简单的 进行重定向。它重定向的是标准输出而不是标准错误。前一节提过每个文件描述符都由一个数字从 0 开始表示。标准输入是 0标准输出是 1标准错误是 2。这意味着可以用略显啰唆的写法来重定向标准输出1而不是简单的 跟上文件名但其实没这个必要便捷写法 就够了。要想重定向标准错误可以使用 2。标准输出和标准错误之间有一处重要不同前者是缓冲式的buffered​后者是非缓冲式的unbuffered​。非缓冲意味着每个字符都是单独写入不会被收集在一起然后再批量写入。也就是说你可以立刻看到错误消息发生故障时丢失此类消息的可能性较小但由此带来的就是效率问题。这并不是说标准输出就不可靠但在发生错误的情况下例如某个程序出乎意料地“挂掉”了​缓冲式输出未必能在程序停止执行前将错误消息呈现在屏幕上。这就是标准错误采用非缓冲式的原因确保能够及时写入消息。相比之下对于标准输出只有缓冲区满了或文件被关闭​才会写入输出。对于频繁用到的输出这么做效率更高但要报告错误时效率并不是最重要的。如果想要同时保存并查看输出该怎么办16 节中讨论过的 tee 命令此时就能派上用场了。gcc bad.c21|teesave.it该命令将标准错误重定向到标准输出然后通过管道将两者传给 tee 命令。tee 会将其输入写入文件save.it和自己的标准输出因为 tee 并没有重定向标准输出所以你也可以在屏幕上看到经由管道传来的输入信息。这是重定向的一种特例因为重定向的顺序通常很重要。比较以下两个命令somecmdmy.file21somecmd21my.file在第一个命令中标准输出被重定向到文件 my.file然后标准错误被重定向到和标准输出相同的地方。所有的输出都会出现在 my.file 中。第二个命令可就不是这样了其中标准错误被重定向到标准输出此时标准输出指向的是屏幕​然后标准输出被重定向到 my.file。因此只有标准输出消息会出现在文件中而错误消息仍旧会出现在屏幕上。但是这种顺序不适用于管道。你不能将第二个重定向放在管道符号后面因为管道之后出现的是下一个命令。因此按照以下方式写入时somecmd21|othercmdbash 对此做了特殊处理它可以识别出标准输出连接到了管道 3。因此当你写出 21 时bash 会假定你希望将标准错误也连入管道尽管 21 的正常处理方式并非如此。命令行中的管道会先于重定向进行处理。这种做法包括一般的管道语法带来的另一个结果是我们无法只将标准错误而非标准输出传入其他命令除非事先交换文件描述符​。bash 4.x 版本中的一种便捷语法可以同时将标准输出和标准错误连入管道。要想将 somecmd 的两种输出流全都传给 othercmd可以使用|somecmd|othercmd20、交换STDERR和STDOUT你需要交换 STDERR 和 STDOUT这样就可以将STDOUT 发送到日志文件然后用 tee 命令将 STDERR 发送到屏幕和另一个文件。但是管道仅适用于 STDOUT。在进入管道前用第三个文件描述符交换 STDERR 和STDOUT./myscript311stdout.logfile23-|tee-astderr.logfile每次重定向文件描述符时就会将打开的描述符复制到另一个描述符。这样我们就得到了一种交换描述符的方法这跟在程序中交换两个值差不多借助一个临时的第三方位置。将 A 复制到 C将 B 复制到 A再将 C 复制到 B这样就交换了 A 和 B 的值。对于文件描述符操作如下./myscript311223将语法 31 读作“使文件描述符 3 指向与标准输出文件描述符 1 相同的值”​。这里是将文件描述符 1STDOUT复制到文件描述符 3也就是我们的临时存放位置。然后再将文件描述符 2STDERR复制到STDOUT最后将文件描述符 3 复制到 STDERR。这样的结果就实现了交换 STDERR 和 STDOUT 文件描述符。更准确地说是交换了 STDERR 和 STDOUT 文件描述符的值。目前一切还算不错。现在我们再略加改动。一旦复制了STDOUT复制到文件描述符 3​就可以放心将STDOUT 重定向到用来捕获脚本或其他程序输出的日志文件。然后我们再将保存在临时位置文件描述符 3的文件描述符复制到 STDERR。添加管道也没有问题因为管道连接到最初的STDOUT。这样就得到了先前看到的解决方案./myscript311stdout.logfile23-|tee-astderr.logfile注意 23- 中结尾的 -。这么做是为了操作完成后关闭文件描述符 3。如此一来程序就没有额外打开的文件描述符了。我们做到了自己的事情自己整理。另外还用到了 tee 的 -a 选项该选项表示追加而不是替换。21、避免意外覆盖文件你害怕错误地删除文件内容。输错文件名发现将输出重定向到了原本打算保存的文件这种事情太常见了。告诉 shell 更加谨慎set-onoclobber如果你觉得完全用不着这么小心翼翼可以关闭该选项seto noclobbernoclobber 选项告诉 bash 在重定向输出时不要覆盖任何现有文件。如果重定向输出的文件尚不存在一切照常进行由 bash 创建该文件并将其打开以供输出之用。如果文件已经存在则产生错误消息。我们来实际演练一下。先关闭该选项这只是为了让 shell处于已知状态不管特定系统先前是怎么配置的$seto noclobber ➊ $echosomethingmy.file ➋ $echosomemoremy.file $set-onoclobber ➌ $echosomethingmy.file bash: my.file: cannot overwrite existingfile$echosomemoremy.file1 ➍ $❶ 第一次我们将输出重定向到 my.filebash 会为我们创建该文件。❷ 第二次我们又将输出重定向到 my.filebash 覆盖了该文件它将文件截断为 0 字节并从头写入​。❸ 设置 noclobber 选项再次试图重定向到该文件时我们得到了错误消息。❹ 就像示例中显示的我们可以用 向该文件中追加内容。注意noclobber 选项仅针对 shell 重定向输出时的文件覆盖行为。它并不能阻止其他程序覆盖文件​$echouseless datasome.file $echoimportant dataother.file $set-onoclobber $cpsome.file other.file $这里并不会出现错误。现有文件被 cp 命令覆盖了。noclobber 选项在这种情况下不起作用。如果你敲代码时又快又好这看起来似乎不是一个重要的选项但在其他实例中我们会探究由正则表达式生成或以变量传递的文件名它们均能用于输出重定向。在这种情况下noclobber 选项就是一个重要的安全特性了可以避免不必要的副作用无论是一时糊涂还是有意为之​。22、有意覆盖文件你乐于设置 noclobber 选项但在重定向输出时偶尔也想覆盖文件。能不能有那么一次可以有意忽略 bash 的好心使用 | 重定向输出。即便是设置了 noclobberbash 也会忽略该选项并覆盖文件。思考以下示例$echosomethingmy.file $set-onoclobber $echosomemore|my.file ➊ $catmy.file somemore$echoonce againmy.file ➋ bash: my.file: cannot overwrite existingfile$注意第二次 echo 并不会产生错误。但在第三次 echo 中我们并未用到 |仅仅使用了 符号。shell 发出了警告也并未覆盖现有文件。noclobber 的使用并不会代替文件权限。不管有没有使用|如果没有目录的写权限那么就无法创建文件。与此类似不管有没有使用 |你必须拥有文件的写权限才能覆盖现有文件。为什么要用 | 据 Chet 所说​“POSIX 指定了语法 |这还是从 ksh88 中选出来的。我也说不清楚 Korn5 为什么会选用这种写法。csh 用的是 !”​。为了帮助记忆你可以将其视为一种强调。它的用法在英语中带有祈使语气符合要求 bash 在必要时“无论如何”都要覆盖文件的意味。vi 和 ex 编辑器在其 write 命令中也用 ! 表达了相同含义:w! filename​。如果没有 !覆盖已有文件时编辑器会发出抱怨。要是加上 !就相当于告诉编辑器“做就行了”​。
返回列表