目录
前言
虽然笔者工作多年,常年接触Shell,但也对I/O重定向符只有最基本的了解,所以本文旨在梳理工作中没接触到的I/O重定向符知识。
如果你已经对I/O重定向符非常了解,也不妨看看本文。也许会有新的发现。
如果你对I/O重定向不太了解,那么推荐你仔细看完本文,你将对I/O重定向有最基本的了解。
参考
本文里系统层面的定义都参照自 Open Group的POSIX-2017 (IEEE Std 1003.1™-2017)标准。
I/O 重定向是什么?
I/O redirection, 译做 I/O 重定向,是shell command language里的概念。如其名,在shell commad language里可以把程序的输入输出的位置重新指定。
下面我们来通过例子来看一下什么是重定向,懂的读者可以跳到下一节。
执行下列命令之后可以看到本该输出到命令行的”Text …“被输出到文件里,并通过cat命令打印内容到终端。
echo "Test Redirection Operator '>>' Shell Command Language" >> ~/echoOutput.txt
ls -l ~ | grep echoOutput.txt
cat ~/echoOutput.txt
rm ~/echoOutput.txt
Shell 与 Shell Command Language
笔者曾一度对cmd.exe、命令行、终端、Shell这些名词的具体定义感到非常恼火,笔者主观感受到这些词在日常生活中被误用地非常严重。
所以笔者将在本节澄清两个概念”Shell“与”Shell Command Language“。
什么是Shell?
Shell,根据Open Group的定义,Shell是一个用于解释字符串输入到具体命令并执行的程序。也被称为Shell命令语言解释器(Shell Command Language Interpreter)。通常Shell是通过终端(Terminal)或文本文件来接收字符串输入的。
什么是Shell Command Language?
Shell Command Language,在Open Group没有对于该词条给出明确的定义,通常我们在终端或者Shell脚本里写的就是Shell Command Language。和别的语言一样,由开发者键入的源代码都是文本。执行的时候需要经过翻译,这个翻译员就是上一节提到的Shell (Command Language Interpreter)。
Shell命令语言,也许”Shell脚本语言“更加常听见,被设计成一种脚本语言,也就是“解释型语言”。与JavaScript等脚本语言类似,不需要像C语言等必须经过编译才能执行。
I/O 重定向符
I/O redirection Operator, 译做I/O 重定向符,下面列举POSIX-2017标准里定义的
No. | 操作符 | 说明 |
---|---|---|
1 | < | 输入流重定向,FD未指定时默认使用0 (standard input)。 |
2 | > | 输出流重定向,文件不存在时会被创建,以覆盖模式写入,FD未指定时默认使用1 (standard output)。 |
3 | >| | 与>类似,仅当因noclobber flag而使>操作符无法覆盖写入时,使用>|操作符可以为单独的文件覆写noclobber flag,使得该文件能被覆盖写入。noclobber flag通过set -C设置。 |
4 | >> | 输出流重定向,文件不存在时会被创建,以追加模式写入,默认FD为1。 |
5 | << | 允许接下来的到标志之前的行被shell读入作为命令的input。 |
6 | <<- | 与<<类似,不过输入行的前置<tab>字符会被无视。 |
7 | <& | 将输入文件合并 |
8 | >& | 将输出文件合并 |
9 | <> | 打开一个文件并分配给指定FD,未指定时默认FD为1。 |
I/O 重定向符使用例
本节将列举的几个重定向符基本使用例,以供参考。
例1:用<指定文件作为输入。
humao% echo "testInput" > testInput.txt
humao% cat testInput.txt
testInput
humao% cat <testInput.txt
testInput
humao% rm testInput.txt
humao%
例2:用>指定文件作为输出,并覆盖原文件。
humao% echo "Original Text" > testOutput.txt
humao% cat testOutput.txt
Original Text
humao% echo "New Text" > testOutput.txt
humao% cat testOutput.txt
New Text
humao% rm testOutput.txt
humao%
例3:用>|指定文件作为输出,并突破覆盖写入限制。
humao% set -C
humao% echo "Original Text" > testOutput.txt
testOutput.txt
humao% cat testOutput.txt
Original Text
humao% echo "New Text" > testOutput.txt
zsh: file exists: testOutput.txt
humao% cat testOutput.txt
Original Text
humao% echo "New Text" >| testOutput.txt
humao% cat testOutput.txt
New Text
humao% rm testOutput.txt
humao%
例4:用>>指定文件作为输出,并在原文件追加写入。
humao% echo "Original Text" > testOutput.txt
humao% cat testOutput.txt
Original Text
humao% echo "Additional Text" >> testOutput.txt
humao% cat testOutput.txt
Original Text
Additional Text
humao% rm testOutput.txt
humao%
例5:用<<指定接下来几行作为输入。
需要注意的是anyTokenForEndInputing可以任意的token,用于标识输入到此结束。
humao% cat <<anyTokenForEndInputing
heredoc> Line1
heredoc> Line2
heredoc> Line3
heredoc> anyTokenForEndInputing
Line1
Line2
Line3
humao%
例6:用<<-指定接下来几行作为输入,并无视行前导的tab字符。
注意:笔者使用的是zsh,在bash下未能成功加入tab字符。
humao% cat <<-anyTokenForEndInputing
heredocd> Line1
heredocd> Line2
heredocd> Line3
heredocd> Line4
heredocd> anyTokenForEndInputing
Line1
Line2
Line3
Line4
humao%
例7:<> 与 <&的使用例
这个例子稍微复杂一点。笔者简单说明一下本节在干什么。
- 创建input.txt并输入一行文字”1234567890“。
- 生成一个shell脚本,并加入两行指令
- 指令1:read指令读取5个字符,通过 <&3 重定向3号FD到read的标准输出上。
- 指令2:echo指令输出read的结果到终端。
- 为脚本增加执行权限。
- 执行脚本。并通过<>重定向符为脚本程序增加一个可读写的文件(input.txt)到3号FD上。
- 删除测试文件。
humao% echo 1234567890 > input.txt
humao% cat input.txt
1234567890
humao% echo "read -n 5 word1 <&3" > testDuplicateRedirectionOperator.sh
humao% echo "echo \$word1" >> testDuplicateRedirectionOperator.sh
humao% cat testDuplicateRedirectionOperator.sh
read -n 5 word1 <&3
echo $word1
humao% chmod u+x testDuplicateRedirectionOperator.sh
humao% ./testDuplicateRedirectionOperator.sh 3<>input.txt
12345
humao% ls
input.txt testDuplicateRedirectionOperator.sh
humao% rm *.*
这一例子比较难,读者需要知道脚本运行时,对于OS来说,也是一个进程,也有PID。
执行read指令的时候,read是作为子进程会继承父进程的FD,所以3号FD也能被read子进程所用。
如果能理解本节说明你对重定向与进程的关系都有一定的理解。
例8:用>&合并stderr和stdout。
本节你可以看到stderr通过>&重定向符与stdout合并的例子。
humao% mkdir cdBandedFolder
humao% chmod u-x cdBandedFolder
humao% cd cdBandedFolder
cd: permission denied: cdBandedFolder
humao% cd cdBandedFolder 2>/dev/null
humao% cd cdBandedFolder 2>&1
cd: permission denied: cdBandedFolder
humao% rmdir cdBandedFolder
结语
对于在终端操作多的人来说,了解I/O重定向是必要的,准确来说,是理解进程的I/O是很重要的,你需要知道你的进程的输入和输出都到哪里去了,并且知道如何控制他们的去向。了解这些基本原理之后,你就能知道比如Eclipse、VS内的Console输出的是怎么来的了。你对自己程序整体性把握将上升一个台阶。
如果你觉得本文对你有启发或帮助,请不要吝惜你的点赞和转发~ ,
如果你觉得本文有错误的地方,也请在评论区指出,笔者会虚心接受并修改错误的地方~。