Linux 命令I/O重定向

当命令被执行时,会打开三个文件描述符,标准输入,标准输出,标准错误。标准输入默认来自键盘,标准输出和标准错误默认为屏幕。我我们可以利用重定向功能来改变命令执行时的标准输入、标准输出和标准错误。

重定向标准输入

重定向标准输入语法如下

[n]<file

它表示在文件描述符 n 上打开文件 file,用于读文件。如果 n 被省略,那么默认在标准输入上打开文件 file。

例如,在执行 cat 命令的时候,如果不附带任何参数,那么它会读取标准输入(键盘)的内容,然后把它显示到标准输出(屏幕)。那么,我们可以重定向它的标准输入为一个文件

$ cat < hello.txt
hello world

当然,cat < hello.txt 这个命令有点冗余,使用 cat hello.txt 也可以达到效果,这里只是为了演示的目的。

语法[n]>file中,符号>和file之间是可以加个空格的。通常似乎为了美观,都会使用这个空格。

重定向输出

重定向输出的语法如下

[n]>file

它表示在文件描述符n上打开文件 file,用于写文件。如果省略 n,那么表示在标准输出上打开文件 file,用于向文件写内容。

我们可以利用这个语法来重定向标准输出和标准错误。例如,ls 命令默认会把输出显示到屏幕(标准输出)上,我们可以利用重定向功能,把 ls 命令的执行结果写到一个特定的文件中。

$ ls -l > ls-result.txt

然而,执行 ls 命令会出错,例如权限拒绝,这些错误信息会从标准错误显示到屏幕上。而上面的命令只是重定向了标准输出,因此错误信息还是会显示到屏幕上

$ ls -l /root/ > ls-result.txt
ls: cannot open directory '/root/': Permission denied

我们利用重定向输出的功能,把标准错误重定向到一个单独的文件中

$ ls -l /root/ > ls-result.txt 2> ls-result-error.txt
$ cat ls-result-error.txt
ls: cannot open directory '/root/': Permission denied

追加重定向输出

如果重定向输出的文件不存在,那么会默认创建它,如果文件已经存在,那么会把文件的大小截断为了0,也就是经常说的文件被重写。

我们可以利用追加重定向输出的功能,把重定向输出追加到一个文件中,而不是重写文件。

追加重定向输出的语法如下

[n]>>>file

它表示在描述符 n 上打开文件 file,用于追加写文件。如果省略 n,那么表示在标准输出上打开文件,向文件里追加写内容。

例如,我们可以把多个 ls 命令执行的结果输出到同一个文件

$ ls -l /usr/ > ls-result.txt
$ ls -l /bin/ >> ls-result.txt

重定向标准输出和标准错误

在前面,我们用了一个很长的命令来分别重定向标准输出和标准错误,命令如下

$ ls -l /root/ > ls-result.txt 2> ls-result-error.txt

其实,大可不必这么复杂,直接把标准输出和标准错误重定向到一个文件中。这就需要重定向标准输出和标准错误功能,语法如下

&>file

或者

>file 2>&1

那么,我们用重定向标准输出和标准错误的语法,来重构上面的复杂的命令

$ ls -l /root/ &> ls-result.txt

追加重定向标准输出和标准错误

同样地,重定向标准输出和标准错误,在文件存在时,会截断文件的长度为0。如果为了避免这种情况的发生,我们可以选择使用追加重定向标准输出和标准错误的功能,语法如下

&>>file

或者

>>file 2>>&1

那么现在我们利用这个语法,把 ls 命令的标准输出和标准错误,追加重定向到一个文件,而不是截断并写入一个文件

ls -l /usr/ /root/ &>> ls-result.txt

Here Documents

Here Documents 语法如下

[n]<<[-]delimiter
	here-document-lines
delimiter

注意,最后一个delimiter的前后都不要有空格。

Here Documents 语法让 shell 去读取 here-document-lines 的内容,作为文件描述符 n 的输入。如果省略 n,那么读取的内容作为标准输入。

Here Documents 其实也是利用了重定向标准输入的功能,它通常在脚本中使用。例如有如下一个脚本,它格式化输出一段 HTML 文本

$ cat here-documents.sh 
#!/bin/bash

cat <<_EOF_
<html>
    <h1>Hello world</h1>
<html>
_EOF_
$
$ ./here-documents.sh 
<html>
    <h1>Hello world</h1>
<html>

如果我们在脚本中使用的是 <<- 而不是 <<,那么输出的结果会去年每一行前面的制表符。

首先我们用 cat -A 查看脚本

bash $ cat -A here-documents.sh 
#!/bin/bash$
$
cat <<-_EOF_$
<html>$
^I<h1>Hello world</h1>$
<html>$
_EOF_$

cat -A 的输出结果中,$ 表示一行的结尾,^I 表示一个水平制表符。

从输出结果中可以看到,我们使用了 Here Documents 的 <<- 而不是 <<,因此执行脚本后,显示的结果中不会有制表符而导致的缩进。

$ ./here-documents.sh 
<html>
<h1>Hello world</h1>
<html>

注意,<<- 不会忽略行前的空格,只会忽略制表符。
很多人喜欢用VIM写shell脚本,但是关于VIM的空格和制表符没有配置好,使用 <<- 会没有效果。
现在很多IDE、文本编辑器默认把制表符设置为4个空格,使用 <<- 也会没有效果。

Here Documents 针对 shell 扩展还有一个奇怪的用法。如果第一个 delimiter,不加引号,那么 Here Documents 的文本行会执行 shell 扩展,例如,下面的脚本使用了参数扩展

$ cat here-documents.sh 
#!/bin/bash

cat <<-_EOF_
Hello $1
_EOF_

$1 表示执行脚本时传入的第一个参数,当我们执行脚本的时候,会把 $1 展开为第一个参数的值

$ ./here-documents.sh David
Hello David

而如果把 Here Documents 语法的第一个 delimiter 加上引号(单引号或双引号),那么 Here Documents 的文本行 不会 执行 shell 扩展

$ cat here-documents.sh 
#!/bin/bash

cat <<-'_EOF_'
Hello $1
_EOF_
$
$ ./here-documents.sh 
Hello $1

Here Strings

Here Strings 语法如下

[n]<<< string

Here Strings 会让 shell 把 string 以及一个换行符,作为文件描述符 n 的输入。如果省略 n,那么使用的是标准输入。

注意,Here Strings 使用三个 >,而 Here Documents 使用两个 <。

我们以 cat 命令为例,cat 会读取参数指定的文件的内容或者标准输入的内容,我们使用 Here String 语法来重定向 cat 的标准输入

$ cat <<< "hello world"
hello world

复制文件描述符

最后介绍下晦涩难懂的概念,复制文件描述符。

我们在介绍重定向标准输出和标准错误的时候,提供有两种语法

&>file

或者

>file 2>&1

第二种语法中,2>&1 表示把标准输出重定向到标准输入,其实它有一个官方名字,叫做复制文件描述符。

对于输出的复制文件描述符语法如下

[n]>&fd

官方的解释特别晦涩难懂,其实它就是把文件描述符 n 的输出重定向到文件描述符 fd上。

与之相对的,输入的复件文件描述符语法如下

[n]<&fd

这个目前我还没有用到过,所以不好举例了。

工作经验

在编译 Android 源码的时候,有时我们既想把日志显示到终端上,又想把日志保存到文件中,我们可以使用 tee 命令。但是 tee 命令只操作标准输出,而日志信息还包含标准错误的信息,因此我们还需要复制文件描述符的功能,把标准错误重定向到标准输出。

make -j8 2>&1 | tee build.log
发布了44 篇原创文章 · 获赞 30 · 访问量 400万+

猜你喜欢

转载自blog.csdn.net/zwlove5280/article/details/103989577