Linux 管道与命令重定向

内容纲要

查看【Linux】专题可浏览更多内容

Linux 有一个有趣的概念,基本上所有的输入和输出(都是文本)实际上都是数据/文本的流。就像水管一样,你可以连接和断开部分,把水重新引向不同的地方,所以你也可以连接和断开数据流。

「重定向」在命令和文件之间改变输入和输出,还可以连接多个命令,形成功能强大的「管道」。

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

运行的每个命令或程序都有三个数据流与之相连:

  • 标准输入:STDIN (0)
  • 标准输出:STDOUT (1)
  • 标准错误:STDERR (2)

许多程序从 STDIN 标准输入获取输入,然后程序将运行结果发送给 STDOUT 标识输出,状态消息则是发给 STDEER 标准错误。

💡 默认情况下,标准输入与键盘相关联,而标准输出和标准错误与显示器屏幕相关联,并不会保存为磁盘文件。

管道与重定向

💡 UNIX 有一个「只做一件事并做到极致」的哲学理念,也就是说一个程序的功能就应该只用于某个单一用途,当既需要 A 又需要 B 时,则让它们「连接」在一起工作。
基于这个理念 UNIX/Linux 上有许多性能非常好的小型实用程序。
再想一想那些让人恼火的「大而全」而又号称「小而美」的软件...

管道

「管道」的作用在于让一个命令的输出作为输入发送到另一个程序。

管道是 Linux 中最有用的命令行功能之一,管道有无数种用途,几乎无处不在。

举个例子,/etc/hosts 文件的内容如下:

127.0.0.1       localhost
127.0.1.1       debian

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

管道操作符:|

我想要查看该文件前五行的最后一行是什么,那么可以这么做:

# 使用管道操作符 |
cat /etc/hosts | head -n 5 | tail -n 1
::1     localhost ip6-localhost ip6-loopback

发生了什么呢?

  1. 首先使用 cat 命令查看 /etc/hosts 文件,但因为 | 管道操作符所以不同于 cat /etc/hosts 命令将结果输出在屏幕上,而是将结果作为输入交给了 head 命令;
  2. head 命令搭配 -n 选项将文件的头五行内容输出作为输入给了 tail 命令;
  3. tail 命令搭配 -n 选项输出 head -n 5 拿到的输入内容的最后一行,后面没有 | 管道操作符了,所以标准输出到了屏幕上;

重定向

命令行的重定向特性对解决某些特定的问题颇有帮助。

很多命令都用到了标准输入和标准输出,绝大多数命令行程序使用标准错误来显示提示性信息。

重定向允许我们修改输出结果的去处和输入的来源。

重定向标准输出操作符:> 与 >>

如把标准输出重定向到其他文件,而非出现在屏幕上,可以使用重定向操作符 >

# 使用重定向操作符
ip address > network.txt

# 查看 network.txt
cat network.txt
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:fb:61:13 brd ff:ff:ff:ff:ff:ff
    altname enp2s1
    inet 192.168.1.1/24 brd 192.168.1.255 scope global dynamic ens33
       valid_lft 1006sec preferred_lft 1006sec
    inet6 fe80::20c:29ff:fefb:6113/64 scope link 
       valid_lft forever preferred_lft forever

使用 > 重定向到同一个文件会发生吗?

echo "hi" > hi.txt
echo "hi" > hi.txt

❓ 此时 hi.txt 文件的内容是什么?
只有一行 hi,因为 > 对于重定向到的文件如果不存在则创建,如果已存在则覆盖,那么如果我想要追加输出结果而不是覆盖呢?

使用 >> 操作符:

echo "hi" > hi.txt
echo "hi" >> hi.txt
echo "hi" >> hi.txt

cat hi.txt
hi
hi
hi

重定向标准错误操作符:2> 与 2>>

文章开头提到过「标准错误」的「文件描述符」是 2

所以当想把标准错误的输出重定向到文件时,可以这么做:

ls /rootly 2> err.txt

cat err.txt
ls: cannot access '/rootly': No such file or directory

命令 ls /rootly 的标准错误信息就重定向到文件 err.txt 中了。

同标准输出操作符一样,如果想要追加标准错误信息而不是覆盖,则使用 2>> 操作符即可。

/dev/null

有时候可能会想要丢掉错误和状态消息,就可以将输出结果重定向到名为 /dev/null 的特殊文件。

/dev/null是一个系统设备,通常称作「位桶(bit bucket)」,能够接收输入结果但不做任何处理。

重定向标准输出和标准错误输出:&> 与 &>>

再来看一个例子,假设当前我使用的是非特权用户,使用命令查看 /root/home 目录:

ls /root /home
/home:
toor
ls: cannot open directory '/root': Permission denied

ls 告诉了我 /home 下有一个 toor(非特权用户的主目录),然后告诉我我没有权限访问 /root 目录。

如果使用 > 标准输出操作符:

ls /root /home > info.txt

它会将 /home 目录下的结果重定向到文件 info.txt,然后在屏幕上输出 ls: cannot open directory '/root': Permission denied

如果我想将标准输出与标准错误重定向分别保存到文件中呢?

ls /root /home > info.txt 2> err.txt

但如果我同时需要标准输出与标准错误重定向到文件中呢?使用 &> 操作符:

ls /root /home &> info.txt
cat info.txt
/home:
toor # 非特权用户的主目录
ls: cannot open directory '/root': Permission denied

💡 在旧版本的 Shell 中,一般会写成:
ls /root /home > info.txt 2>&1
这表示先将 /home 的结果重定向到到 info.txt 文件中,然后使用 2>&1 将文件描述符 2(标准错误)重定向到文件描述符 1(标准输出)

这样就同时得到标准输出与标准错误的结果了。

和之前一样,如果想要追加内容而不是覆盖,使用 &>> 操作符。