linux简单理解输入输出重定向
linux
重定向可以分为输出重定向和输入重定向。在系统中除了标准输入、标准输出、错误输出以外,其他的重定向,都需要指定文件描述符id
,也可以称为句柄。
在linux
中,可以通过查看/proc/进程pid号/fd/
下的文件来查看该进程有多少个fd
,比如:
bash
# ls -l /proc/$$/fd/
total 0
lrwx------. 1 root root 64 Jun 3 22:22 0 -> /dev/pts/0
lrwx------. 1 root root 64 Jun 3 22:22 1 -> /dev/pts/0
lrwx------. 1 root root 64 Jun 3 22:22 2 -> /dev/pts/0
#
上述命令中$$
表示该进程本身的pid
,而该fd
文件下,可以看到有0
、1
、2
三个文件,都指向/dev/pts/0
,这是一个字符设备文件,表示当前的终端。
输出重定向原理
对于系统而言,任何一个程序都有2个输出,分别是:
- 文件描述符为1的标准输出
- 文件描述符为2的错误输出
所以,当执行一个命令输出重定向的时候,比如:
bash
command >> filename
上述命令是将command
命令执行的标准输出结果重定向到filename
文件中,你可以理解为在执行该命令的时候,其进程标准输出文件描述符1
由默认的/dev/pts/0
指向了filename
文件,为此可以来写一个程序来试试。
程序如下:
bash
#!/bin/bash
echo "pid: $$" >&2
while true
do
echo 123
sleep 3
done
这个程序非常简单,首先现输出该脚本的pid
到错误输出上,即文件描述符为2,而后定一个无限循环,每隔3秒输出123
。
在执行的时候,将该文件输出重定向到filename
中。
bash
# bash test.sh >> filename
pid: 42699
程序执行会卡主,这是正常的,这是因为有无限循环,每隔3秒会输出123
字符串至标准输出上。
而后打开一个新的终端,查看42699
的fd
文件信息。
bash
# ls -l /proc/42699/fd/
total 0
lrwx------. 1 root root 64 Jun 3 23:41 0 -> /dev/pts/0
l-wx------. 1 root root 64 Jun 3 23:41 1 -> /root/fd/filename
lrwx------. 1 root root 64 Jun 3 23:41 2 -> /dev/pts/0
lr-x------. 1 root root 64 Jun 3 23:41 255 -> /root/fd/test.sh
#
通过上面执行的结果可以发现,其标准输出,文件描述符为1
已经指向一个文件:/root/fd/filename
,而非终端字符设备。所以说,在上面案例中,重定向后,实际上是将句柄给改写到文件中,所以屏幕没有输出任何数据。基于此,标准输入和错误输出的原理也是这样的。
输出重定向
输出重定向一般格式为:[n]>[|]filename
,其中n
是文件描述符,>|
表示覆盖保护绕过机制,不过一般不用,即常用的写法为:[n]>filename
。
比如,将字符串abcdef
的标准输出重定向到filename
中:
bash
echo "abcdef" 1> filename
默认情况下,1>
可以缩略写为>
,这也是常用的写法:
bash
echo "abcde" > filename
将命令dasdsada
的错误结果写入到文件filename
中:
bash
dasdsada 2> filename
因为没有这个命令,所以一定会报错-bash: dasdsada: command not found
。
对于该重定向而言,它在执行前会清理掉原始内容,从而写入新的内容。
除此之外,还有追加重定向,其格式为[n]>>filename
,其中n
是文件描述符,>>
表示追加,它不会清理掉原有内容。比如将abc
追加到文件filename
中。
bash
echo 123 1>> filename
同样的,也可以将1>>
缩略写为>>
。
将命令aaa
的错误结果追加写入到文件filename
中:
bash
aaa 2>> filename
输入重定向
在linux
系统中,任何一个程序的标准输入文件描述符id
为0
,而输入重定向一般格式为[n]<filename
,其中n
表示文件描述符。
所以,如下命令:
bash
cat 0< filename
它的意思是将filename
文件的内容作为标准输入提供给cat
调用,结果是打印了filename
的内容,上述命令中的0<
可以缩略写为<
。
不仅如此,还能在循环中,搭配read
,将文件的信息按行读取。
比如有以下文件内容:
bash
# cat filename
aa
bb
cc
dd
#
有脚本内容如下:
bash
#!/bin/bash
while read ll
do
echo read_at: ${ll}
done 0< filename
上述脚本表示将filename
文件作为输入源,供while
调用,按照的方式是read
命令按行调用,将行内容赋值到ll
变量上,这个变量名称可以自定义。
其结果为:
bash
# bash test.sh
read_at: aa
read_at: bb
read_at: cc
read_at: dd
#
输入重定向还有一种表达方式是here-documents
。表示多行定义输入,其语法为:
bash
command [n]<<word
here doc
word
其中n
表示文件操作符,如果省略,则使用默认的文件操作符0
,而后是关键字<<
,word
是自定义的结束符,让后续读取到word
后停止读取。
比如:
bash
cat <<END
123
456
789
END
输出的结果为
bash
123
456
789
上述命令中的END
是可以替换为任何字符串的,只要后续出现该字符串就会停止输入,注意这个字符,一定是要在单独的一行出现的,不能有其他字符,包括空格。
here-documents
会进行命令替换,变量替换等,比如有如下命令:
bash
# x=123
# cat <<END
${x}
$(pwd)
aa
bb
cc
END
输出的结果会先进行命令替换,变量替换等,比如将${x}
替换为123
,将$(pwd)
替换为当前目录。如果不想其被替换,则需要添加单引号进行转义。
重定向的顺序
注意看,如下有2条命令:
bash
ls -l > filename 2>&1
和
bash
ls -l 2>&1 > filename
所执行的结果是完全不一样的,第一条命令是将标准输出和错误输出都写入到filename
中,而第二条则是将错误输出输出到屏幕上,而将标准输出写入到文件中,如果不是很明白,可以尝试拆解一下命令:
首先,命令的前提是:标准输出 和 错误输出 都在某个设备上,比如pty
终端,假设设备为/dev/pts/0
,所以文件描述符信息为:
1 -> /dev/pts/0
2 -> /dev/pts/0
然后第一条命令,ls -l > filename
其实是将文件描述符1
给指向了文件filename
,这个时候,文件描述符信息为:
1 -> filename
2 -> /dev/pts/0
这个时候再使用2>&1
表示将错误消息也写入到文件描述符为1
的文件中,即filename
,所以最后的文件描述符为:
1 -> filename
2 -> filename
而第二条命令,一开始都是输出到某个设备上:
1 -> /dev/pts/0
2 -> /dev/pts/0
这个时候,它将错误输出指向2>&1
,而文件描述符为1
是/dev/pts/0
,所以这第一步,就没动过,而第二部则是将标准输出指向filename
,即:
1 -> filename
2 -> /dev/pts/0
所以第二条命令,错误输出输出到屏幕上,而将标准输出写入到文件中。
总结
要理解输入输出重定向,一定要理解文件的描述符!对于每个进程,都会产生3个默认的文件描述符,分别是 标准输入、标准输出 和 错误输出,用文件描述符id
:0
、1
、2
来表示。
其重定向就是修改这些文件描述符的指向,将他们指向到文件中即可。
对于输入重定向而言,有一种表达方式是here-documents
,它可以和输出重定向结合,将终端中输入的文件写入到文件中,比如:
bash
cat 1>> filename 0<<END
a
b
c
d
e
END
表示将终端输入的a
、b
... e
字符提供给cat
命令,而cat
将内容重定向到文件filename
中。可以发现上面有1>>
表示将标准输出的内容重定向到某个文件,0<<
表示将标准输入的内容重定向到某个文件,上述命令中的1>>
可以缩写为>>
,而0<<
可以缩写为<<
。