# 第四部分：重定向、管道与文本处理

## 数据流重定向

你可能对重定向这个概念感到些许陌生，但你应该在前面的课程中多次见过 $>$ 或 ${ } > > { }$ 操作了，并知道他们分别是将标准输出导向一个文件或追加到一个文件中。这其实就是（输出）重定向，将原本输出到标准输出的数据重定向到一个文件中，因为标准输出(/dev/stdout)本身也是一个文件，我们将命令输出导向另一个文件自然也是没有任何问题的。

上述两个重定向是输出重定向。相应的还有 $<$ 和 $< <$ 两种操作，是输入重定向。

在 Linux 系统中默认提供了三个逻辑设备（特殊文件），用于终端的显示和输出，分别为stdin （标准输入,默认对应于终端作为输入），stdout （标准输出，默认对应于终端作为输出），stderr （标准错误输出，默认对应于终端作为输出）。

<table><tr><td>文件描述符</td><td>设备文件</td><td>说明</td><td>常见指向目标</td></tr><tr><td>0</td><td>/dev/stdin</td><td>标准输入</td><td>终端设备/dev/pts/1</td></tr><tr><td>1</td><td>dev/stdout</td><td>标准输出</td><td>终端设备</td></tr><tr><td>2</td><td>dev/stderr</td><td>标准错误</td><td>终端设备</td></tr></table>

文件描述符：在形式上是一个非负整数。实际上，它是一个索引值，指向内核为每一个进程所维护的记录表，表中记录该进程打开文件的信息。当进程打开一个现有文件或者创建一个新文件时，内核就会向进程返回一个文件描述符。在程序设计中，一些涉及底层的程序 编 写往往会围绕着 文 件描述符展开 。 但 是 文 件描述符这 一概念往往只 适 用 于UNIX、Linux 这样的操作系统。

操作符、文件存在时的行为、文件不存在时的行为：

> 覆盖原内容 创建新文件  
>> 追加到原内容末尾 创建新文件

 默认使用终端的标准输入stdin 作为命令的输入和标准输出作为命令的输出：

cat

（按 $\mathsf { C t r l + C }$ 退出）

 将 cat 接收的输入又输出（heredoc 的方式）重定向到一个文件：

mkdir Documents

cat $>$ Documents/test.c <<EOF

...内容...

EOF

1. cat $>$ Documents/test.c 表示将 cat 命令的输出重定向到 Documents/test.c文件（如果文件不存在则创建）  
1. <<EOF（输入重定向） 是 "here document" 语法，它表示：

o 开始一个多行输入  
o 将后续所有内容作为cat 的输入  
o 直到遇到单独一行的 EOF 为止（这个标记词可以自定义，常用 EOF 或END）

 将一个文件作为命令的输入，标准输出作为命令的输出：

cat Documents/test.c

上述命令也可以作为查看短文件内容的方法，但是如果文件内容很长就会滚屏到最后一屏。

 将echo 命令通过管道传过来的数据作为 cat 命令的输入，将标准输出作为命令的输出：

echo 'hi' | cat

1. echo 'hi' 输出字符串 hi\n 到标准输出  
2. 管道 | 将前一个命令的标准输出重定向为下一个命令的标准输入  
3. cat 检测到自己没有文件名参数，于是开始读取标准输入  
4. 将读取到的内容（hi\n）原样输出到标准输出（终端）

 将 echo 命令的输出从默认的标准输出重定向到一个普通文件：

echo 'hello shiyanlou' $>$ redirect

cat redirect

也就是说，cat和echo都可以重定向输出

管道默认是连接前一个命令的输出到下一个命令的输入，而重定向通常是需要一个文件来建立两个命令的连接。

重定向标准输出到文件，这是一个很实用的操作，另一个很实用的操作是将标准错误重定向，标准输出和标准错误都被指向伪终端的屏幕显示，所以我们经常看到的一个命令的输出通常是同时包含了标准输出和标准错误的结果的。比如下面的操作：

使用 cat 命令同时读取两个文件，其中一个存在，另一个不存在：

cat Documents/test.c hello.c

可以看到，除了正确输出前一个文件的内容，末尾还出现了一条错误信息。

下面将输出重定向到一个文件：

cat Documents/test.c hello.c $>$ somefile

遗憾的是，这里依然出现了那条错误信息，这正是因为如我上面说的那样，标准输出和标准错误虽然都指向终端屏幕，实际它们并不一样。那有的时候我们就是要隐藏某些错误或者警告，那又该怎么做呢。这就需要用到我们前面讲的文件描述符了：

将标准错误重定向到标准输出，再将标准输出重定向到文件。注意，要将重定向到文件写在前面：

cat Documents/test.c hello.c $>$ somefile $2 { > } 8 1$

### 软件安装方式与 sudo 说明只用bash提供的特殊的重定向符号"&"将标准错误和标准输出同时重定向到文件

cat Documents/test.c hello.c $\& >$ somefilehell

注意你应该在输出重定向文件描述符前加上&,否则 shell 会当做重定向到一个文件名为 1的文件中

方法 1：仅重定向标准错误 $( 2 > )$

cat Documents/test.c hello.c $^ { 2 > }$ error.log

方法 2：丢弃标准错误（ $2 >$ /dev/null）

如果不想保存错误信息，而是直接丢弃：

cat Documents/test.c hello.c $^ { 2 > }$ /dev/null

 /dev/null 是一个特殊设备，写入它的数据会被丢弃。

你可能还有这样的需求，除了需要将输出重定向到文件,也需要将信息打印在终端。那么你可以使用tee命令来实现：

echo 'hello shiyanlou' | tee hello

你应该可以看出我们前面的重定向操作都只是临时性的，即只对当前命令有效，那如何做到“永久”有效呢，比如在一个脚本中，你需要某一部分的命令的输出全部进行重定向，难道要让你在每个命令上面加上临时重定向的操作嘛，当然不需要，我们可以使用 exec命令实现“永久”重定向。exec命令的作用是使用指定的命令替换当前的 Shell，即使用一个进程替换当前进程，或者指定新的重定向：

先开启一个子 Shell：

zsh

使用 `exec` 替换当前进程的重定向，将标准输出重定向到一个文件：

exec 1>somefile

之后执行的命令，其输出都将被重定向到文件中，直到退出当前子 Shell，或取消 `exec` 的重定向。

ls

exit

cat somefile

为什么没有新开终端窗口？

##  zsh 命令的本质：

它只是在当前终端中新建一个 Zsh 子进程，而不是像图形化终端（如 GNOMETerminal）那样创建一个新窗口。

**输入/输出继承：**

子 Shell 默认绑定到父 Shell 的同一个终端设备（如 /dev/pts/1），所以输入/输出仍在原窗口

**方法 1：检查进程层级**

在子 Shell 中运行：

pstree -ps $$

在 Shell 中有 9 个文件描述符。上面我们使用了也是它默认提供的 0,1,2 号文件描述符。另外我们还可以使用 3-8 的文件描述符，只是它们默认没有打开而已。你可以使用下面命令查看当前Shell 进程中打开的文件描述符：

cd /dev/fd/;ls -Al

同样使用 exec 命令可以创建新的文件描述符：

zsh

exec $\mathsf { 3 > }$ somefile

先进入目录再查看，否则可能无法得到正确结果；完成后再返回上一次所在的目录。

cd /dev/fd/;ls -Al;cd -

### kill 使用注意下面的命令>与&之间不应该有空格，如果有空格则会出错

echo "this is test" $> 8 . 3$

cat somefile

exit

如上面我们打开的 3 号文件描述符，可以使用如下操作将它关闭：

exec $3 > 8 \cdot$ -

cd /dev/fd;ls -Al;cd -

在 Linux 中有一个被称为“黑洞”的设备文件,所有导入它的数据都将被“吞噬”。

在类 UNIX 系统中，/dev/null，或称空设备，是一个特殊的设备文件，它通常被用于丢弃不需要的输出流，或作为用于输入流的空文件，这些操作通常由重定向完成。读取它则会立即得到一个 EOF。

我们可以利用/dev/null屏蔽命令的输出：

cat Documents/test.c 1>/dev/null $2 { > } 8 1$

当我们需要使用apt-get安装一个软件，然后安装完成后立即运行安装的软件（或命令工

具），

这时你可能就会想：要是我可以一次性输入完，让它自己去依次执行各命令就好了，这就是这一小节要解决的问题。

简单的顺序执行可以使用;，比如上述操作可以写为：

## 命令顺序执行

sudo apt-get update;sudo apt-get install some-tool;some-tool

但是有时候这样的错误并不是直观可以判断出来的。因此需要能够有选择性的执行命令，比如上一条命令执行成功才继续下一条，或者不成功又该做出其它什么处理,比如我们使用which来查找是否安装了某个命令，如果找到就执行该命令，否则什么也不做（虽然这个操作没有什么实际意义，但可帮你更好的理解一些概念）：

which cowsay $>$ /dev/null && cowsay -f head-in ohch~

cowsay 没有是 1 有是 0

上面命令中的 && 就是用来实现选择性执行的，它表示如果前面的命令执行结果（不是表示终端输出的内容，而是表示命令执行状态的结果）返回 0 则执行后面的命令，否则不继续执行。上一次命令的返回结果从 $\$ 7$ 环境变量获取.

而且还有一个 || 表示逻辑或，同样 Shell 也有一个 ||，它们的区别就在于，shell 中的这两个符号除了也可用于表示逻辑与和或之外，就是可以实现这里的命令执行顺序的简单控制。|| 在这里就是与&& 相反的控制效果，当上一条命令执行结果为 $\neq 0 ( \$ 20)$ 时则执行它后面的命令：

![](ab750f6726b902e0fc0de54e6354b6ff90d9bafd3e35cdf5693a2fc44eeed82b.jpg)

which cowsay>/dev/null && echo "exist" || echo "not exist"

如果存在 ——echo “exists”成立，返回 0——后面那一句不成立

如果不存在——echo “exists”不成立，返回 1——后面那一句成立

如果二者互换

which cowsay>/dev/null || echo "not exist" && echo "exist"

如果存在——echo “not exist”不成立,返回 1——后面也不成立

如果不存在—echo “not exist”成立，返回 0——后面成立

如果二者互换

which cowsay>/dev/null || echo "exist" && echo "not exist"

那完全就反了

如果存在——echo “exists”不成立，返回 1——后面也不成立

管道是什么？管道是一种通信机制，通常用于进程间的通信（也可通过 socket 进行网络通信），它表现出来的形式就是将前面每一个进程的输出(stdout)直接作为下一个进程的输入(stdin)。

管道又分为匿名管道和具名管道（这里将不会讨论在源程序中使用系统调用创建并使用管道的情况，它与命令行的管道在内核中实际都是采用相同的机制）。我们在使用一些过滤程序时经常会用到的就是匿名管道，在命令行中由 | 分隔符表示， | 在前面的内容中我们已经多次使用到了。具名管道简单的说就是有名字的管道，通常只会在源程序中用到具名管道。下面我们就将通过一些常用的可以使用管道的"过滤程序"来帮助你熟练管道的使用。先试用一下管道，比如查看/etc 目录下有哪些文件和目录，使用ls 命令来查看：ls -al /etc

有太多内容，屏幕不能完全显示，这时候可以使用滚动条或快捷键滚动窗口来查看。不过这时候可以使用管道：

```txt
Is -al /etc | less 
```

通过管道将前一个命令(ls)的输出作为下一个命令(less)的输入，然后就可以一行一行地看。

```txt
cut命令
```

打印每一行的某一字段

打印/etc/passwd 文件中以: 为分隔符的第 1 个字段和第 6 个字段分别表示用户名和其家目录：

```batch
cut /etc/passwd -d ':-' -f 1,6 
```

打印 /etc/passwd 文件中每一行的前 N 个字符：

```txt
前五个（包含第五个）
```

```batch
cut /etc/passwd -c -5 
```

```txt
前五个之后的（包含第五个）
```

```txt
cut /etc/passwd -c 5- 
```

```txt
第五个
```

```batch
cut /etc/passwd -c 5 
```

```txt
2到5之间的（包含第五个）
```

```batch
cut /etc/passwd -c 2-5 
```

输出时，cut 直接拼接 "one" 和 "three"，不添加任何字符，所以结果是 "onethree"但你的测试显示输出是"one:three"，这说明：你的cut 版本（可能是某些定制版或不同环境）可能行为略有不同

```txt
-d, -- delimiter=DELIM 
```

作用：指定字段的分隔符（默认是 TAB 制表符）。

```txt
-f, --fields=LIST 
```

作用：选择要提取的字段（列），可以指定单个字段、多个字段或范围。

## grep 命令

grep（global search regular expression）是一个强大的文本搜索工具。grep 使用正则表达式搜索文本，并把匹配的行打印出来。

UNIX 的 grep 家族包括 grep、egrep 和 fgrep：

grep 使用 Basic regular expression (BRE) 书写匹配模式，等效于 grep -G

egrep 使用 Extended regular expression (ERE) 书写匹配模式，等效于 grep -E

fgrep 不使用任何正则表达式书写匹配模式（以固定字符串对待），执行快速搜索，等效于 grep -F

在文本中或 stdin 中查找匹配字符串 grep 命令的一般形式为：

grep [命令选项]... 用于匹配的表达式 [文件]...

grep [options] PATTERN [FILE...]

PATTERN 是查找条件

可以是普通字符串

可以是正则表达式，通常用单引号将RE括起来。

FILE 是要查找的文件，可以是用空格间隔的多个文件，也可是使用Shell的通配符在多个文件中查找PATTERN，省略时表示在标准输入中查找。

grep 命令不会对输入文件进行任何修改或影响，可以使用输出重定向将结果存为文件。

<table><tr><td>-c</td><td>只显示匹配行的次数</td></tr><tr><td>-i</td><td>搜索时不区分大小写</td></tr><tr><td>-n</td><td>输出匹配行的行号</td></tr><tr><td>-v</td><td>输出不匹配的行（反向选择）</td></tr><tr><td>-r</td><td>对目录（子目录）的所有文件递归地进行</td></tr><tr><td>-l</td><td>列出匹配PATTERN的文件名</td></tr><tr><td>--color=auto</td><td>对匹配内容高亮显示</td></tr><tr><td>-A NUM</td><td>同时输出匹配行的后NUM行</td></tr><tr><td>-B NUM</td><td>同时输出匹配行的前NUM行</td></tr><tr><td>-C NUM</td><td>同时输出匹配行的前、后各NUM行</td></tr></table>

grep在文件中查找字符串

grep 表达式 目标文件

grep “abc” file1

ls –l | grep “^-r.x”

xargs 是一条 UNIX 和类 UNIX 操作系统的常用命令。它的作用是将参数列表转换成小块分段传递给其他命令，以避免参数列表过长的问题。

这 个命令在 有些时候十分 有 用 ，特别是当用 来处理 产生大量输出 结果的命令如 fin

d，locate 和 grep 的结果。

cut -d: -f1 $<$ /etc/passwd | sort | xargs echo

上面这个命令用于将 /etc/passwd 文件按 : 分割取第一个字段排序后，使用 echo 命令生成一个列表。

文本分析命令  

<table><tr><td>命令</td><td>功能</td></tr><tr><td>wc</td><td>统计文本</td></tr><tr><td>sort</td><td>以行为单位对文本文件排序</td></tr><tr><td>uniq</td><td>删除文本文件中连续的重复的行</td></tr><tr><td>diff</td><td>比较两个文本文件的差异</td></tr><tr><td>diff3</td><td>比较三个文本文件的差异</td></tr><tr><td>patch</td><td>为文本文件打补丁</td></tr><tr><td>aspell</td><td>为文本文件做拼写检查（西文）</td></tr></table>

## wc 命令

用于统计并输出一个文件中行、单词和字节的数目，比如输出 /etc/passwd 文件的统计信

息：

wc /etc/passwd

功能：统计文本文件的行数、字数、字符数

格式：wc [选项] [<文件> …]

举例

$\$ 1$ wc file

$\$ 1$ wc -l file

#### 统计行数

$\$ 1$ wc -w file

#### 统计字数

$\$ 1$ wc -c file

#### 统计字符数

$\$ 1$ wc -L file

#### 统计最长一行的长度

### sort 排序

功能：以行为单位对文件进行排序

格式：sort [选项] [<文件> …]

选项

<table><tr><td>-r</td><td>逆向排序</td></tr><tr><td>-f</td><td>忽略字母的大小写</td></tr><tr><td>-n</td><td>根据字符串的数值进行排序</td></tr><tr><td>-u</td><td>对相同的行只输出一行</td></tr><tr><td>-t c</td><td>选项使用c做为列的间隔符</td></tr><tr><td>-b</td><td>忽略前导的空格</td></tr><tr><td>-i</td><td>只考虑可打印字符</td></tr><tr><td>-k N</td><td>以第N列进行排序（默认以空格或制表符作为列的间隔符）</td></tr></table>

默认为字典排序：

cat /etc/passwd | sort

反转排序：

cat /etc/passwd | sort -r

按特定字段排序：

cat /etc/passwd | sort -t':' -k 3

上面的 -t 参数用于指定字段的分隔符，这里是以":"作为分隔符；-k 字段号 用于指定对哪一个字段进行排序。这里 /etc/passwd 文件的第三个字段为数字，默认情况下是以字典序排序的，如果要按照数字排序就要加上-n 参数：

cat /etc/passwd | sort -t':' -k 3 -n

uniq 命令可以用于过滤或者输出重复行。

过滤重复行

### history 命令

我们可以使用 history 命令查看最近执行过的命令（实际为读取${SHELL}_history 文件,如我们环境中的~/.zsh_history 文件），不过你可能只想查看使用了哪个命令而不需要知道具体干了什么，那么你可能就会要想去掉命令后面的参数然后去掉重复的命令：

history | cut -c 8- | cut -d ' ' -f 1 | uniq

然后经过层层过滤，你会发现确是只输出了执行的命令那一列，不过去重效果好像不明显仔细看你会发现它确实去重了，只是不那么明显，之所以不明显是因为uniq命令只能去连续重复的行，不是全文去重，所以要达到预期效果，我们先排序：

history | cut -c 8- | cut -d ' ' -f 1 | sort | uniq

或者：

history | cut -c 8- | cut -d ' ' -f 1 | sort -u

这就是 Linux/UNIX 哲学吸引人的地方，大繁至简，一个命令只干一件事却能干到最好。

输出重复行

输出重复行（重复内容只输出一次）及重复次数：

history | cut -c 8- | cut -d ' ' -f 1 | sort | uniq -dc

输出所有重复的行：

history | cut -c 8- | cut -d ' ' -f 1 | sort | uniq -D

没有 sort 则会重复

## 比较 compare

cmp 发现第一处不同停止

comm 显示两个文件的相同与不同之处

diff 按行比较不同，显示所有不同的行的信息

## 文本文件处理命令

命令

功能

<table><tr><td>tr</td><td>字符替换</td></tr><tr><td>sed</td><td>流编辑器，常用于字符串替换</td></tr><tr><td>paste</td><td>纵向合并多个文本</td></tr><tr><td>expand</td><td>将文件中的制表符转换为空格</td></tr><tr><td>unexpand</td><td>将文件中的空格转换为制表符</td></tr><tr><td>dos2unix</td><td>将 DOS 格式的文本转换成 UNIX 格式</td></tr><tr><td>unix2dos</td><td>将 UNIX 格式的文本转换成 DOS 格式</td></tr><tr><td>iconv</td><td>将文本从一种编码转换成另一种编码</td></tr></table>

## Sed 命令

sed 是一个流编辑器（stream editor）。sed 是一个非交互式的行编辑器，它在命令行中输入编辑命令、指定被处理的输入文件，然后在屏幕上查看输出。输入文件可以是指定的文件名，也可以来自一个管道的 输出。

与vi 不同的是 sed 能够过滤来自管道的输入。在 sed 编辑器运行的时候不必人工干涉，所以 sed 常常被称作批编辑器 。

sed 默认不改变输入文件的内容，且总是将处理结果输出到标准输出，可以使用输出重定向将sed 的输出保存到文件中。

#### 格式

sed [选项] [-e] cmd1 [[-e cmd2] ... [-e cmdn]] [input-file]...

#### 说明

在命令行上执行sed编辑命令。可以指定多个编辑命令，每个编辑命令前都要使用 -e 参数，sed 将对这些编辑命令依次进行处理。若只有一个编辑命令时，-e 可以省略。

每个sed的编辑命令cmdX均应使用单引号括起来。

input-file：sed 处理的文件列表，若省略，sed 将从标准输入中读取输入，也可以从输入重定向或管道获得输入。

## 选项

-r：使用扩展正则表达式进行模式匹配

-i：直接对输入文件进行sed的命令操作

iconv 命令

功能：将文件从一种编码转换成另一种编码

格式：iconv [选项] <输入文件>

## 选项

-f <encoding> : 指定原始文本编码。  
-t <encoding> : 指定要转换的编码。  
-o <output file> : 指定输出文件，而不是在标准输出上显示。  
-l : 列出所有已知编码字符集。
