Skip to content

数据流重定向

数据流重定向(redirect)由字面上的意思来看,好像就是数据给它定向到其他地方去的样子?
没错,数据流重定向就是将某个命令执行后应该出现在屏幕上的数据,给它传输到其他的地方,例如文件或是设备(例如打印机之类的)。
这玩意儿在 Linux 的命令行模式下面很重要,尤其是如果我们想要将某些数据存储下来时,就更有用了。

文件标识符和标准输入输出

文件标识符是重定向中很重要的一个概念,Linux 使用 0 到 9 的整数指明了与特定进程相关的数据流,系统在启动一个进程的同时会为该进程打开三个文件:标准输入(stdin)、标准输出(stdout)、标准错误输出(stderr),分别用文件标识符 0、1、2 来标识。
如果要为进程打开其他的输入输出,则需要从整数 3 开始标识。
默认情况下,标准输入为键盘,标准输出和错误输出为显示器

我们执行一个命令的时候,这个命令可能会由文件读入数据,经过处理之后,再将数据输出到屏幕上。

简单地说,标准输出指的是命令执行所返回的正确信息,而标准错误可理解为命令执行失败后,所返回的标准错误

举个简单的例子来说,我们的系统默认有/etc/crontab但却无/etc/vbirdsay,此时若执行cat /etc/crontab /etc/vbirdsay这个命令时,cat 会进行

  • 标准输出:读取/etc/crontab后,将该文件内容显示到屏幕上
  • 标准输出错误:因为无法找到/etc/vbirdsay,因此在屏幕上显示错误信息
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
➜  ~ cat /etc/crontab /etc/vbirdsay
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name command to be executed
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6    * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6    1 * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
#
cat: /etc/vbirdsay: No such file or directory

不管正确或错误的数据都是默认输出到屏幕上,所以屏幕当然是乱的。那能不能通过某些机制将这两股数据分开?
当然可以,那就是数据流重定向的功能,数据流重定向可以将 standard output(简称 stdout)与 standard error output(简称 stderr)分别传送到其他的文件或设备,而分别传送所用的特殊字符则如下所示:

  • 标准输入(stdin): 代码为 0,使用<<<
  • 标准输出(stdout): 代码为 1,使用>>>
  • 标准输出错误(stderr): 代码为 2,使用2>2>>

I/O 重定向符号和用法

I/O 重定向常见符号和功能描述

符号 含义
> 标准输出覆盖重定向:将命令的输出重定向输出到其他文件中
>> 标准输出追加重定向
>& 标识输出重定向:将一个标识的输出重定向到另一个标识的输入
< 标准输入重定向:命令将从指定文件中读取输入而不是从键盘输入
| 管道:从一个命令中读取输出并作为另一个命令的输入

标准输出覆盖重定向: >

使用标准输出覆盖重定向符号可以将原本输出到显示器上的内容重定向到一个文件中,比如使用ls -l可以列出目录中文件的详细信息,但是如果想把结果保存到文件中以便日后查看,则可以使用标准输出覆盖重定向符。示例如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
➜  ~ ls -l /usr/
total 0
lrwxr-xr-x     1 root  wheel     25 11  2  2019 X11 -> ../private/var/select/X11
lrwxr-xr-x     1 root  wheel     25 11  2  2019 X11R6 -> ../private/var/select/X11
drwxr-xr-x  1014 root  wheel  32448  6 14 17:08 bin
drwxr-xr-x   312 root  wheel   9984  6 14 17:11 lib
drwxr-xr-x   264 root  wheel   8448  6 14 17:08 libexec
drwxr-xr-x    18 root  wheel    576  6 14 17:11 local
drwxr-xr-x   234 root  wheel   7488  6 14 17:08 sbin
drwxr-xr-x    46 root  wheel   1472 11  2  2019 share
drwxr-xr-x     5 root  wheel    160 10 24  2019 standalone
➜  Desktop ls -l /usr/ > ls_usr.txt
➜  Desktop cat ls_usr.txt
total 0
lrwxr-xr-x     1 root  wheel     25 11  2  2019 X11 -> ../private/var/select/X11
lrwxr-xr-x     1 root  wheel     25 11  2  2019 X11R6 -> ../private/var/select/X11
drwxr-xr-x  1014 root  wheel  32448  6 14 17:08 bin
drwxr-xr-x   312 root  wheel   9984  6 14 17:11 lib
drwxr-xr-x   264 root  wheel   8448  6 14 17:08 libexec
drwxr-xr-x    18 root  wheel    576  6 14 17:11 local
drwxr-xr-x   234 root  wheel   7488  6 14 17:08 sbin
drwxr-xr-x    46 root  wheel   1472 11  2  2019 share
drwxr-xr-x     5 root  wheel    160 10 24  2019 standalone

请注意,如果指定的重定向文件不存在,则命令会先创建这个文件,如果文件存在且内容不为空,则原文件内容将被全部清空。
所以有时候需要先判断该文件是否存在,以避免不小心破坏了原有文件内容

下面尝试ls一个不存在的文件,看看会发生什么

1
2
3
➜  Desktop ls -l /usr/no_exist > ls_no_exist.txt
ls: /usr/no_exist: No such file or directory
➜  Desktop cat ls_no_exist.txt

这里ls命令发现指定的文件不存在后给出了错误输出。这是为什么呢?

标准输出覆盖重定向其实是默认将文件标识符为 1 的内容重定向到指定文件中,所以如下两种写法是等价的,或者说,第一种写法是第二种写法的“省略写法”

1
2
3
➜  Desktop ls -l /usr/ > ls_usr.txt
# 以上写法等价于
➜  Desktop ls -l /usr/ 1> ls_usr.txt

如果命令由于各种原因出错时所产生的错误输出,其文件标识符为 2,而标准错误的输出默认也是显示器。
所以我们可以通过指定将文件标识符为 2 的内容重定向到指定文件,这样错误输出就不会出现在显示器上了。如下所示:

1
2
3
4
# 错误输出被重定向到文件中
➜  Desktop ls -l /usr/no_exist 2> no_exist_err.txt
➜  Desktop cat no_exist_err.txt
ls: /usr/no_exist: No such file or directory

如果某命令的输出既有标准输出,又有标准错误输出,则可以分别指定不同标识符的内容输出到不同的文件中

1
COMMAND 1> stdout.txt 2> stderr.txt

标准输出追加重定向: >>

该符号用法和>完全一致,不同的只是如果指定的重定向文件存在且内容不为空,重定向并不会清空原文件内容,而是将命令的输出新增到原文件的尾部。
在下面的例子中,先后将/usr/tmp目录列出的内容追加重定向到apend.txt文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
➜  Desktop ls -l /usr/ >> append.txt
➜  Desktop ls -l /tmp/ >> append.txt
➜  Desktop cat append.txt
total 0
lrwxr-xr-x     1 root  wheel     25 11  2  2019 X11 -> ../private/var/select/X11
lrwxr-xr-x     1 root  wheel     25 11  2  2019 X11R6 -> ../private/var/select/X11
drwxr-xr-x  1014 root  wheel  32448  6 14 17:08 bin
drwxr-xr-x   312 root  wheel   9984  6 14 17:11 lib
drwxr-xr-x   264 root  wheel   8448  6 14 17:08 libexec
drwxr-xr-x    18 root  wheel    576  6 14 17:11 local
drwxr-xr-x   234 root  wheel   7488  6 14 17:08 sbin
drwxr-xr-x    46 root  wheel   1472 11  2  2019 share
drwxr-xr-x     5 root  wheel    160 10 24  2019 standalone
total 16
drwx------  3 nocilantro  wheel   96  8  2 11:34 com.apple.launchd.bEf0tkGprF
drwx------  3 nocilantro  wheel   96  8  2 11:34 com.apple.launchd.sEvd5dpgl9
drwxr-xr-x@ 4 nocilantro  wheel  128  8  2 11:35 com.google.Keystone
srwxrwxrwx  1 nocilantro  wheel    0  8  2 11:35 mysql.sock
-rw-------  1 nocilantro  wheel    4  8  2 11:35 mysql.sock.lock
srwxrwxrwx  1 nocilantro  wheel    0  8  2 11:35 mysqlx.sock
-rw-------  1 nocilantro  wheel    5  8  2 11:35 mysqlx.sock.lock
srwxr-xr-x  1 nocilantro  wheel    0  8  2 13:21 nemu-nocilantro-audio-vpipe
srwxr-xr-x  1 nocilantro  wheel    0  8  2 13:21 nemu-nocilantro-gles-vpipe
drwxr-xr-x  2 root        wheel   64  8  2 11:34 powerlog

标识输出重定向: >&

标识输出重定向的作用是将一个标识的输出重定向到另一个标识的输入。
比如想要将标准输出和标准错误同时定向到同一个文件中,可使用如下命令:

1
COMMAND > stdout_stderr.txt 2>&1 

上面演示的命令从左到右可以读为:执行 COMMAND 命令,将标准输出的内容重定向到 std_stderr.txt 中,如果又标准输出也同时重定向到该文件中

为演示执行某个命令后既有标准输出又有错误输出的情景,可以使用普通用户执行以下命令,从输出内容可以看到,除了标准输出外还有很多文件权限问题而出现的错误输出

1
2
3
4
5
6
➜  Desktop find / -type f -name *.txt
find: /usr/sbin/authserver: Permission denied
find: /Library/Application Support/Apple/ParentalControls/Users: Permission denied
find: /Library/Application Support/Apple/AssetCache/Data: Permission denied
find: /Library/Application Support/ApplePushService: Permission denied
...

现试图将上述所有输出重定向到某个文件中,使用以下命令只能将标准输出覆盖重定向到find_res01.txt文件,而大量的错误输出依然会出现在显示器上。

1
2
3
4
5
6
7
8
9
➜  Desktop find / -type f -name *.txt > find_res01.txt
find: /usr/sbin/authserver: Permission denied
find: /Library/Application Support/Apple/ParentalControls/Users: Permission denied
find: /Library/Application Support/Apple/AssetCache/Data: Permission denied
find: /Library/Application Support/ApplePushService: Permission denied
find: /Library/Application Support/com.apple.TCC: Operation not permitted
find: /Library/OSAnalytics/Preferences/Library: Permission denied
find: /Library/Caches/com.apple.iconservices.store: Permission denied
...

下面使用标识输出重定向,将标准错误输出同时定向到find_res01.txt文件

1
2
find / -type f -name *.txt > find_res01.txt 2>&1
# 此时屏幕上看不到任何输出

/dev/null

很多时候大家并不会在意错误输出,特别是一些系统后台任务可能在每天凌晨运行,这时出现的错误可能并不是系统管理员所关心的,所以也没有必要将错误输出保存到任何文件中。
这时可以利用系统中的一个特殊设备/dev/null,将所有错误输出重定向到该设备中--系统会将任何输入到该设备的内容全部删除(就像一个宇宙黑洞)

1
COMMAND > stdout.txt 2> /dev/null

标准输入重定向: <

标准输入重定向可以将原本应由从标准输入设备中读取的内容转由文件内容输入,也就是将文件内容写入标准输入中。

下面的例子中首先运行 cat 命令,系统将等待键盘输入。
如果此时输入 Hello 并回车,cat 命令会读取并立即输出 Hello,然后命令将继续等待输入,直到使用 Crtl+D 组合键终止输入

1
2
3
4
5
6
7
➜  Desktop cat
Hello
Hello
World
World
# Ctrl + D
# 终止输入

在下面的例子中,先将要打印的内容提前写到某个文件中,比如hello_world01.txt,然后使用标准输入重定向将内容重定向给 cat 命令。
从命令输出结果可以看出,文件内容被标准输入重定向给了 cat 命令,然后该命令忠实地履行了打印任务

1
2
3
4
5
6
➜  Desktop cat hello_world01.txt
Hello
World
➜  Desktop cat < hello_world01.txt
Hello
World

下面是使用 sort 排序的例子,运行 sort 命令后系统将等待键盘输入,依次输入 3 个单词并以回车符隔开,输入结束后使用 Ctrl+D 组合键终止输入。
此时 sort 命令会打印出排序后的单词列表

1
2
3
4
5
6
7
8
➜  Desktop sort
banana
apple
carrot
# Ctrl + D 终止输入
apple
banana
carrot

如果将需要排序的单词预先写到fruit01.txt文件中,然后使用标准输入重定向给 sort 命令,效果和之前一致,如下所示:

1
2
3
4
5
6
7
8
➜  Desktop cat fruit01.txt
banana
apple
carrot
➜  Desktop sort < fruit01.txt
apple
banana
carrot

结束的输入字符: <<

举例来说,我要用 cat 直接将输入的信息输出到 catfile 中,且当由键盘输入 eof 时,该次输入就结束,那我可以这样做:

1
2
3
4
5
6
7
➜  Desktop cat > catfile << "eof"
heredoc> hhh
heredoc> 233
heredoc> eof
➜  Desktop cat catfile
hhh
233

利用<<右侧的控制字符,我们可以终止一次输入,而不必按下Crtl + D来结束,这对程序写作很有帮助