news 2026/5/20 7:01:30

第15章 标准IO:重定向和管道

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第15章 标准IO:重定向和管道

从一开始,Unix 命令行就具备一些特殊的东西,使其区别与其他操作系统,即所谓的的 Unix工具箱:每种 Unix 和 Linux 系统都拥有的大量程序。本章将解释 Unix 工具箱之后隐藏的设计准则,然后师范如何将基本的构建块组合成适合于自己的功能强大的工具。

Unix 设计准则

每个程序都应该是一个单独的工具,或许还有几个基本的选项。一个程序应该只做一件事,但必须出色地完成这个事情。四需要执行一项复杂的任务,应该通过对现有工具的组合来完成(当存在可能性时),而不是再去编写一个新程序。

more 程序(输出显示工具):每次一屏地显示数据,按键就会显示下一屏幕数据;按q键就可以退出。

因此,Unix 的设计准则可以用两句话概括:

  • 每个程序或者命令应该是一个工具,它只完成一件事情,但一定要完成好这件事情;
  • 当需要新工具时,最好对现有的工具进行组合,而不是编写一个新工具;

有时候将这一设计准则描述为:

  • “Small is beautiful(小的就是完美的)” 或者 “Less is more(少的就更好)”;

Unix 新设计准则

CLI(命令行界面)是基于文本的,它无法处理图形和图像,或者没有包含存文本的文件。大多数命令行程序读取和写入文本,这就是为什么这样的程序可以继承在一起工作的原因:他们都使用相同类型的数据。但是当希望处理非文本数据时,必须使用其他类型的程序,所以需要学习 CLI 和 GUI。

常用屏幕显示工具:

  • more:使用简单,所有系统上都有这个程序
  • pg
  • less:许多系统上,less 是默认的程序,而且 less 程序更加出色

Unix 新设计准则:

  • “除非程序无法更小,否则小的就是完美的”

标准输入、标准输出和标准错误

Unix 的开发人员设计了一种读取数据的通用方法(称之为标准输入)和两种写出数据的通用方法(称之为标准输出和标准错误),综合起来,我们称这些功能为标准I/O(standard I/O)。

重定向标准输出

在登录时,shell 会自动地将标准输入设置为键盘,将标准输出和标准错误设置为屏幕。这意味着,默认情况下,大多数程序从键盘读取输入,并将输出写入到屏幕。

但是,每次输入命令时,都可以告诉 shell 在此命令执行期间重置标准输入、标准输出或标准错误。

如果希望命令的输出写到屏幕上,则无需做任何事情,这是自动的。

如果想将命令的输出写入到文件中,需要在命令后面键入 >(大于号)字符,后面跟文件名:

sort>names# 该命令将它的输出写入到一个叫 names 的文件中

写入到的目的文件可以存在,也可以不存在。如果不存在,shell 将自动创建这个文件;如果存在,那么它的内容将被替换。如果希望将新数据添加到原有的数据后面,需要用 >> 字符,即两个连续的大于号字符:

sort>>names# 将新数据追加在已有文件的尾部

将标准输出发送给文件时,即为重定向标准输出。

如果希望 shell 永远不替换现有文件的内容,可以通过设置 noclobber 选项(Bash、Korn shell)或者 shell 变量 noclobber(C-Shell、Tcsh)。

set-o noclobber

如果设置了此选项,并且重定向标准输出到一个已经存在的文件,那么 Bash 将拒绝执行命令,并给出如下消息:

bash: names: cannot overwrite existing file

重定向标准输入

默认情况下,标准输入被设置成键盘。这意味着,当运行需要读取数据的程序时,程序期望用户通过键盘输入数据,每次一行。但结束输入数据时,可以按 ^D(组合键)发送 eof 信号。

重定向标准输入,使命令从文件中读取数据而不是键盘,只需要在命令的最后键入 <(小于号),后面跟着文件的名称即可:

sort<names# 从 names 文件中读取内容sort</etc/passwd# 从 /etc/passwd 文件中读取内容

标准输入和标准输出可以同时重定向:

sort<rawdata>report

重定向标准错误、文件描述符

shell 提供两种不同的输出目标:标准输出和标准错误。标准输出用于正常输出,标准错误用于错误消息。两者皆可重定向。但重定向标准错误的语法不相同。

在 Unix 进程中,每个输入源和每个输出目标都由一个唯一的数字标识,这个数字称为文件描述符(file descriptor)。

文件描述符的本质代表着进程对 I/O 资源的引用。在 Linux/Unix 系统中,文件描述符是一个非负整数,它代表了进程与一个文件、管道、套接字(Socket)或其他 I/O 资源之间的连接。关注如下的命令:

calculate8>results

此命令在执行之前,shell 解析后会为这个进程创建值为8的文件描述符,并使其与 results 文件关联。其中的 > 表明这个文件是用于写入的,即打开文件的模式是只读还是只写的,另外,如果要创建一个可读可写的文件描述符,可以使用 <>:

calculate8<>results

在 Bourne shell 家族中,重定向输入或输出的正式语法是在文件描述符数字之后使用 <(小于号)或 >(大于号)。默认情况下,Unix 为每个进程提供3个预定义的文件描述符:0 代表标准输入;1 代表标准输出;2 代表标准错误;

因此,重定向标准IO 的写法如下:

command0<inputfile# 重定向标准输入,同 command < inputfilecommand1>outputfile# 重定向标准输出,同 command > outputfilecommand2>outputfile# 重定向标准错误

为了方便起见,如果重定向输入时省略了 0,那么 shell 假定指的就是标准输入;如果重定向输入时省略 1,那么 shell 假定指的就是标准输出。

同一个条命令可以指定多个重定向:

sort0<rawdata1>results2>errorssort<rawdata>results2>errors

以上重定向只适用于 Bourne shell,在 C-Shell 家族中重定向标准错误比较复杂。

子 shell

进程就是加载到内存中并且准备运行的程序,以及程序的数据和跟踪程序所需的信息。当进程需要启动另一个进程时,这个进程就创建一个副本进程。原始的进程称为父进程,副本进程称为子进程。

子进程开始运行后,父进程等待子进程死亡(也就是结束)。一旦子进程死亡,父进程就会被唤醒,重新获得控制权并再次开始运行,此时子进程消失。

shell 在解析命令时,判断命令是内部命令还是外部命令,内部命令 shell 就会在自己的进程中直接解释执行。而外部命令 shell 需要查找合适的程序,然后以一个新进程运行这个程序,当该程序终止时,shell 重新获得控制权,此时shell 就是父进程,shell 运行的程序就是子进程。

当一个 shell 启动另一个 shell 时,我们称第二个 shell 为 子shell。当创建子 shell 时,它集成父shell 的环境,但是,子shell 对环境的任何改变都不会传递回父shell。

除了直接输入命令启动一个新的 shell,还有一个方法让命令在 子shell 中运行:将命令括在圆括号中。这样就可以告诉 shell 在子shell 中运行命令:

(date)# 在子shell 中运行 date 命令

重定向标准输出和标准错误到同一个文件

对于 Bourne Shell 其基本思想就是将一种类型的输出重定向到一个文件,然后再将另一种类型的输出重定向到同一个位置:

commandx>outputfile y>&xsort1>output2>&1# 重定向标准输出和标准错误到文件 outputsort&>output# bash 4.0 以上,可以使用此写法,也可以使用 sort >& output# 错误!在一条命令中重定向同一个文件两次的话,一个重定向会覆盖另一个重定向sort>output2>output

其中 &1 标识文件描述符1当前指向的地址。

抛弃输出

当需要抛弃输出时,只需要重定向输出到一个特殊的文件:/dev/null;对于此文件,发送给它的任何东西都会永远消失。

另外,特殊文件 /dev/tty 表示终端,数据发送给次文件时,输出就显示在显示器上。

Bourne Shell 家族的重定向总结

这里只提供 Bourne shell 家族的重定向总结,C-Shell 不再给出。以下为 Bourne shell 家族的标准IO的重定向:

元字符动作
<重定向标准输入(同 0<)
>重定向标准输出(同 1>)
>|重定向标准输出:强制重写
>>追加标准输出(同 1>>)
2>重定向标准错误
2>>追加标准错误
2>&1(原书有错误)将标准错误重定向到标准输出
>& 或者 &>重定向标准输出 + 标准错误(只适用于 bash)
|将标准输出通过管道传送给另一条命令
2>&1 |将标准输出+标准错误通过管道传送给另一条命令

大多数命令行程序使用标准IO进行输入和输出,输入来源于标准输入(stdin)、正常输出发送到标准输出(stdout)、错误消息则发送给标准错误(stderr)。

对于 Bourne shell 家族,可以使用文件描述符(stdin=0、stdout=1,stderr=2)及各种元字符控制标准IO。当没有歧义性时,可以省略文件描述符,为了防止不小心重写了已有的文件,可以设置 shell 选项 noclobber。如果设置了 noclobber 选项,则可以使用 >| 强制进行重写。

  • ls(list,列举)命令显示文件的信息,使用 -l(long)选项,ls可以显示文件的信息。
  • touch 命令可以用来创建一个空文件。
  • cat 命令可以用于查看某个文件的内容。
  • rm(remove,移除)命令用于删除文件。

管道线

Unix 开发人员的目标就是构建小的工具,每个工具只做一件事情,当一个工具无法解决的问题时,能够使用一组工具来完成这个任务。

例如,假设有3个文件,每个文件中的每一行都是一个人的信息,现在要找出有多少人叫 Harley。

首先,使用 cat(catenate,连接)命令将这几个文件组合在一起;然后使用 grep 命令抽取所有包含单词 Harley 的行;最后,使用 wc(word count,单词统计)命令和 -l(line count,行统计)选项统计行的数量,即:

catfile1 file2 file3>tempfile1# combine filesgrepHarley<tempfile1>rempfile2# extract lineswc-l<tempfile2# count linesrmtempfile1 tempfile2# delete temp files

注意,这些命令使用重定向在临时文件中存储中间结果,当工作完成时再将临时文件删除。
上面的命令序列可以完成任务,但是有一个缺陷:将所有事情连接在一起粘合剂是使用临时文件进行重定向,这种方式难以理解,且复杂。

为了使这样的解决方法简单些,shell 允许创建一序列命令,在这一序列命令中,一个程序的标准输出可以自动地发送给下一个程序的标准输入。当这样做时,两个程序之间的连接就是管道(pipe),而命令序列本身称为管道线(pipeline)。创建管道线,只需要将命令之间使用 | 字符(管道符号)分隔开即可。

例如,上面的命令序列可以使用下列命令替代:

catfile1 file2 file3|grepHarley|wc-l

在 Unix/Linux 命令行中,管道符(|)的作用是将一个命令的标准输出(Standard Output, stdout)连接到另一个命令的标准输入(Standard Input, stdin)。在上面的命令中,三个程序是在各自独立的进程中执行的。

对于 Bourne shell 家族来说,可以将标准输出和标准错误组合在一起,然后一起发送给另一个程序:

command12>&1|command2# command1 和 command2 都是命令

当创建管道线时,必须使用能够从标准输入读取文本,并向标准输出写入文本的程序。我们称这样的程序为 过滤器,许多程序都是过滤器。

实际中,大多数管道线只连续使用2条或者3条命令。到目前为止,管道线最常见的应用就是将一些命令的输出传递给 less,从而可以每次一屏地显示命令的输出:

cal2008|less

管道线分流:tee

有时候,可能希望将程序的输出同时发送到两个地方。例如希望将一个输出保存在文件中,同时还发送到另一个程序。

使用 tee 命令可以实现这目的。tee 命令的作用就是从标准输入读取数据,并向标准输出和一个文件各发送一份数据,其语法为:

tee[-a]file...# file 是希望将数据发送到的文件的名称

例如上面的问题中:

catnames1 names2 names3|grepHarley# 显示三个文件中包含Harley 的所有行# 加入 tee 进行分流catnames1 names2 names3|teemasterlist|grepHarley# 此时 cat 的输出保存在 masterlist 中,同时还将传送给 grep

使用 tee 时,通过指定更多个文件名可以为输出保存不止一份副本:

# 在此管道线中,tee 将 cat 的输出复制到两个文件 d1 和 d2 中catnames1 names2 names3|teed1 d2|grepHarley

如果在 tee 命令中指定的文件不存在,那么 tee 命令会创建这个文件。但是,使用时必须小心,如果文件存在,那么 tee 命令将会重写这个文件,原始内容就会丢失。如果希望 tee 命令在文件的末尾追加数据,而不是替换文件,则可以使用 -a(append,追加)选项,例如:

# 在此管道线中,tee 将 cat 的输出保存在 backup 文件中,如果该文件存在,则追加到文件的末尾catnames1 names2 names3|tee-a backup|grepHarley

注意下面的这个模式:

command|teefile# 将 command 的输出保存在文件中并输出到屏幕上who|teestatus# 将 who 的输出保存在 status 文件中并显示在屏幕上

在此模式下,不必在 tee 之后使用另一个程序。因为 tee 将它的输出发送给标准输出,默认情况下就是屏幕。

管道线的重要性

实际上,管道线的实现是 Unix 设计准则提升的催化剂。在很大程序上,正式管道线和标准IO使Unix 的命令行界面功能如此强大。

条件执行

有时候,希望在前一条命令成功执行的条件下执行另一条命令,语法为:

command1&&command2

有时候,希望在前一条命令没有成功执行的条件下执行另一条命令,语法为:

command1||command2

这里就是当前一条命令成功执行或失败时才执行另一条命令,这就是所谓的条件执行。
观察如下命令:

# 查找 people 文件中的 Harley 行,如果存在,就对 people 内容进行排序并输出到 contacts 文件中grepHarley people&&sortpeople>contacts# 与上一个命令相同,不同的是此命令不显示 grep 的输出grepHarley people>/dev/null&&sortpeople>contacts# 如果 update 命令执行失败,就显示提示update|echo"The update program failed."

命令的成功或失败由其退出状态码决定,0 表示成功,非 0 表示失败。command1 && command2 依赖 command1 的退出码为 0 来决定是否执行 command2。你可以通过 echo $? 检查上一个命令的退出码,或参考命令的文档了解具体退出码含义。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/12 22:15:31

BGP实验基础配置

实验拓扑实验要求 1、AS1中存在两个环回&#xff0c;一个地址为192.168.1.0/24&#xff0c;该地址不能在任何协议中宣告AS3中存在两个环回&#xff0c;一个地址为192.168.2.0/24&#xff0c;该地址不能在任何协议中宣告&#xff0c;最终要求这两个环回可以ping通2、R1-R8的建邻…

作者头像 李华
网站建设 2026/5/12 13:50:37

揭秘空间转录组批次效应:如何用R语言实现精准校正与可视化

第一章&#xff1a;揭秘空间转录组批次效应&#xff1a;挑战与意义空间转录组技术的快速发展为研究基因表达在组织空间中的分布提供了前所未有的分辨率。然而&#xff0c;实验过程中不可避免地引入批次效应——即不同实验批次间的技术变异&#xff0c;可能掩盖真实的生物学差异…

作者头像 李华
网站建设 2026/5/19 9:11:29

好写作AI|告别格式炼狱:你的论文如何一秒切换“学术皮肤”

在APA、MLA、国标间反复横跳&#xff1f;被参考文献格式逼疯&#xff1f;你的“智能排版师”已就位&#xff01;各位在格式深渊里挣扎的学术人&#xff0c;是否经历过这样的绝望&#xff1a;论文内容明明不错&#xff0c;却因格式问题被导师打回重改&#xff1b;投稿前夜还在手…

作者头像 李华
网站建设 2026/5/12 13:49:54

为什么顶级投行都在用R做风险模拟?深度解析蒙特卡洛方法的五大优势

第一章&#xff1a;为什么顶级投行青睐R语言进行风险模拟在金融工程与量化分析领域&#xff0c;R语言已成为顶级投行进行风险模拟的首选工具。其强大的统计建模能力、丰富的金融扩展包以及灵活的数据处理机制&#xff0c;使其在复杂市场环境下的风险评估中表现出色。卓越的统计…

作者头像 李华
网站建设 2026/5/19 8:21:15

【DevSecOps必修课】:基于Docker Scout的5阶段漏洞修复体系构建

第一章&#xff1a;DevSecOps视角下的容器安全挑战在现代软件交付流程中&#xff0c;容器技术已成为DevOps实践的核心组件。然而&#xff0c;随着容器化部署的普及&#xff0c;安全问题不再局限于传统基础设施层面&#xff0c;而是贯穿于开发、构建、部署与运行的全生命周期。从…

作者头像 李华
网站建设 2026/5/7 23:59:17

【归并排序】【快速排序】

【归并排序】【快速排序】 详细讲解见以下视频链接 归并排序视频链接 快速排序视频链接 个人理解&#xff1a; 归并排序&#xff1a;先分再排 快速排序&#xff1a;先排再分 归并排序代码&#xff1a; #include <bits/stdc.h> #define int long long using namespac…

作者头像 李华