1. 定义:
Linux重定向是指修改默认选项,对执行方式进行修改,将输入输出重定向到所期望的场景。
2. 文件描述符(file description,fd)
文件描述符是IO重定向中的重要概念。文件描述符使用数字表示,它指明了数据的流向特征。
软件设计认为,程序应该有一个数据来源、数据出口和报告错误的地方。在Linux系统中,它们分别使用描述符0、1、2来表示,这3个描述符默认的目标文件(设备)分别是/dev/stdin、/dev/stdout、/dev/stderr,它们分别是各个终端字符设备的软链接,分别代表的是标准输入,标准输出与标准错误。
┌──(root💀kali)-[~]
└─# ll /dev/std* 16 ⨯
lrwxrwxrwx 1 root root 15 Mar 4 12:58 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 Mar 4 12:58 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Mar 4 12:58 /dev/stdout -> /proc/self/fd/1
┌──(root💀kali)-[~]
└─# ll /proc/self/fd/
total 0
lrwx------ 1 root root 64 Mar 4 13:01 0 -> /dev/pts/0
lrwx------ 1 root root 64 Mar 4 13:01 1 -> /dev/pts/0
lrwx------ 1 root root 64 Mar 4 13:01 2 -> /dev/pts/0
lr-x------ 1 root root 64 Mar 4 13:01 3 -> /proc/2602/fd
在Linux中,每一个进程打开时都会自动获取3个文件描述符0、1和2,分别表示标准输入、标准输出、和标准错误,如果要打开其他文件,则文件描述符必须从3开始标识。对于我们人为要打开的描述符,建议使用9以内的描述符,超过9的描述符可能已经被系统内部分配给其他进程。
类型 | 文件描述符 | 默认情况 | 对应文件句柄位置 |
---|---|---|---|
标准输入(standard input) | 0 | 从键盘获得输入 | /proc/self/fd/0 |
标准输出(standard output) | 1 | 输出到屏幕(即控制台) | /proc/self/fd/1 |
错误输出(error output) | 2 | 输出到屏幕(即控制台) | /proc/self/fd/2 |
文件描述符就是系统为了跟踪这个打开的文件而分配给它的一个数字,这个数字和文件绑定在一起,数据流入描述符的时候也表示流入文件。而Linux中万物皆文件,这些文件都可以分配描述符,包括套接字。
输出重定向:
命令 | 介绍 |
---|---|
command >file | 把标准输出(stdout)重定向到 file 文件中; |
command 1>file | 同上; |
command >>file | 把 stdout 重定向到 file 文件中(追加); |
command 1>>file | 同上; |
command 2>file | 把标准错误(stderr)重定向到 file 文件中; |
command 2>>file | 把 stderr重定向到 file 文件中(追加); |
command >> file 2>&1 | 把 stdout 和 stderr 一起重定向到 file 文件中(追加); |
我们使用>
或者>>
对输出进行重定向。符号的左边表示文件描述符,如果没有的话表示1,也就是标准输出,符号的右边可以是一个文件,也可以是一个输出设备(Linux中万物皆文件,即设备也是文件)。当使用>时,会判断右边的文件存不存在,如果存在的话就先删除,然后创建一个新的文件,不存在的话则直接创建。但是当使用>>进行追加时,则不会删除原来已经存在的文件。
格式:
command-line [n]> 文件
这条命令意思是:将一条命令执行结果(标准输出,或者错误输出,本来都要打印到屏幕上面的) 重定向其它输出设备(文件)1,2分别是标准输出,错误输出。
实例:
#查看文件内容,1.txt 2.txt (其中2.txt文件不存在)
[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt
this is a.txt
cat: 2.txt: No such file or directory
#标准输出与错误输出都显示在屏幕了,
#现在需要把标准输出写入到1.log中
# 1>可以省略,表示标准输出
[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt 1>1.log
cat: 2.txt: No such file or directory
[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt >1.log
cat: 2.txt: No such file or directory
[root@sccprocddev02:/home/upro01]cat 1.log
this is a.txt
#标准输出不输出到屏幕,输出到1.log中
#错误输出不输出到屏幕,输出到2.log中
[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt 1>1.log 2>2.log
[root@sccprocddev02:/home/upro01]cat 1.log 2.log
this is a.txt
cat: 2.txt: No such file or directory
#将标准输出和错误输出分别追加到文件1.log和2.log中 “>>”追加操作符
#可以看到两个文件分别多出一行输出的内容
[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt 1>>1.log 2>>2.log
[root@sccprocddev02:/home/upro01]cat 1.log 2.log
this is a.txt
this is a.txt
cat: 2.txt: No such file or directory
cat: 2.txt: No such file or directory
#高级用法(具体讲解后面会提到)
#将错误输出信息关闭掉,控制台只打印了标准输出
[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt 2>&-
this is a.txt
[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt 2>/dev/null
this is a.txt
#&[n] 代表是已经存在的文件描述符,&1 代表输出 &2代表错误输出&-代表关闭与它绑定的描述符
#/dev/null 这个设备,是linux 中黑洞设备,什么信息只要输出给这个设备,都会给吃掉
#关闭所有输出
#关闭 1 ,2 文件描述符
[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt 1>&- 2>&-
#将1,2 输出转发给/dev/null设备
[chengmo@centos5 shell]$ ls test.sh test1.sh 2>/dev/null 1>/dev/null
[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt >/dev/null 2>&1
#将标准输出fd=1重定向到/dev/null文件,然后将fd=2重定向到fd=1所绑定的/dev/null文件。这种常用文件描述符前必须有个 &, 否则2>1就变成将错误输出输出到一个名为1的文件。
[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt &>/dev/null
#&>代表将标准输出与标准错误重定向到/dev/null文件
输入重定向
在理解了输出重定向之后,理解输入重定向就会容易得多。对输入重定向的基本命令如下:
命令 | 介绍 |
---|---|
command <filename | 以filename文件作为标准输入; |
command 0<filename | 同上; |
command < file >file2 | command命令以 file 文件作为标准输入(stdin),以 file2文件作为stdout; |
cat <>file | 以读的方式打开 file; |
我们使用<
对输入做重定向,如果符号左边没有写值,那么默认就是0。
格式:
command-line [n] <文件
命令默认从键盘获得的输入,使用输入重定向改成从文件,或者其它打开文件以及设备输入。执行这个命令,将标准输入0,与文件或设备绑定,将由它进行输入。
实例:
[root@sccprocddev02:/home/upro01]cat > stdout.txt
this is stdout.txt
^C
[root@sccprocddev02:/home/upro01]cat stdout.txt
this is stdout.txt
#这里使用ctrl+d 或者ctrl+c退出输入
#从标准输入[键盘]获得数据,然后输出给stdout.txt文件
[root@sccprocddev02:/home/upro01]cat > stdout2.txt < stdout.txt
[root@sccprocddev02:/home/upro01]cat stdout2.txt
this is stdout.txt
#从stdout.txt获得输入数据,然后输出给文件stdout2.txt
[root@sccprocddev02:/home/upro01]cat > stdout3.txt << end
> first line
> second line
> end
[root@sccprocddev02:/home/upro01]#cat stdout3.txt
first line
second line
#<< 这个连续两个小符号, 他代表的是[结束的输入字符]的意思。这样当空行输入end字符时,自动退出输入,不需要使用ctrl+d或者ctrl+c退出
3. 文件描述符的复制(duplicate)
文件描述符的复制表示复制文件描述符到另一个文件描述符中,也就是将该文件描述符重定向到另一个文件描述符所绑定的文件,使用”&”进行复制。
[n]<&[m]
:将文件描述符n复制于m代表的文件描述符。可以理解为文件描述符n重定向到m代表的文件描述符所绑定的文件,即m原来对应哪个文件,现在n也对应这个文件。n不指定则默认为0(标准输入就是0),表示标准输入也将输入到m所对应的文件中。[n]>&[m]
:将文件描述符n复制于m代表的文件描述符。可以理解为文件描述符n重定向到m代表的文件描述符所绑定的文件,即m原来对应哪个文件,现在n也对应这个文件。n不指定则默认为1(标准输出就是1),表示标准输出也将输出到m所对应的文件中。
例如,
3>&1
表示fd=3复制于fd=1,而fd=1目前的重定向目标文件是/dev/stdout(fd=1指向与输出设备是默认的),因此fd=3也重定向到/dev/stdout,以后进程将数据写入fd=3的时候,将直接输出到屏幕。如果后面改变了fd=1的输出目标(如file1),由于fd=3的目标仍然是/dev/stdout,所以可以拿fd=3来还原fd=1使其目标变回/dev/stdout。
(fd=1) --> /dev/stdout
|
3>&1
|
(fd=3) --> /dev/stdout
关于文件描述符的duplicate
在操作系统(或C)中,对于实体文件的文件描述符来说,文件描述符是用来描述它所指向的实体文件的。例如fd=5指向文件a.txt。复制(duplicate)实际上是执行dup()函数,表示创建另一个文件描述符(例如fd=6),指向同一个底层对象,例如指向同一个实体文件。这时fd=5和fd=6都将指向a.txt。
在shell中,我们将文件描述符和实体文件的关联关系(或者称为指向的关系)称为重定向,其实用更底层的指向关系更容易理解。例如,”3>&1”表示复制fd=1,使得fd=3和fd=1都指向同一个对象,也就是stdout。
再例如,
cat <&1
表示fd=0复制于fd=1上,而此时fd=1的重定向文件是/dev/stdout,所以fd=0也指向这个/dev/stdout文件,而cat从fd=0中读取标准输入,于是/dev/stdout既是标准输入设备,也是标准输出设备,也就是说进程从/dev/stdout(屏幕)接受输入,输入后再直接输出到/dev/stdout。以下是结果:
┌──(root💀kali)-[~]
└─# cat <&1 130 ⨯
uegwfwuggi
uegwfwuggi
4. 重定向顺序很重要:>file 2>&1
和2>&1 >file
想必很多人都知道>file 2>&1
的作用,它等价于&>file
,表示标准输出和标准错误都重定向到file中。那它和2>&1 >file
有什么区别呢?
首先解释>file 2>&1
。这里分两个过程:先打开file,再将fd=1重定向到file文件上,这样file文件就成了标准输出的输出目标;之后再将fd=2复制于fd=1,而fd=1此时已经重定向到file文件上,因此fd=2也重定向到file上。所以,最终的结果是标准输出重定向到file上,标准错误也重定向到file上。
再解释2>&1 >file
。这里也分两个过程:先将fd=2复制于fd=1,而此时fd=1重定向的文件是默认的/dev/stdout,所以fd=2也重定向到/dev/stdout;之后再将fd=1重定向到file文件上。也就是说,这里的标准错误和标准输出仍然是分开输出的,只不过是使用/dev/stdout替代了/dev/stderr,使用file替代了/dev/stdout。所以,最终的结果是标准错误输出到/dev/stdout,即屏幕上,而标准输出将输出到file文件中。
可以使用下面的命令来测试2>&1 >file
。第一个ls命令是正确的,结果输出到/tmp/fff.log中,第二个ls命令是错误的,结果将直接输出到屏幕上。
┌──(root💀kali)-[~]
└─# ls /boot 2>&1 >/tmp/fff.log 130 ⨯
┌──(root💀kali)-[~]
└─# ls haha 2>&1 >/tmp/fff.log
ls: cannot access 'haha': No such file or directory
最后需要说明的是一种特殊情况,如果是>&[word]
,且word不是一个数值,比如echo haha >&/tmp/fff.log
,那么>&word
和&>word
是等价的,都表示>word 2>&1
,即标准错误和标准输出都重定向同一个目标。参考man bash的”Redirecting Standard Output and Standard Error”段落。
5. 改变当前shell环境的重定向目标
如果在命令中直接改变重定向的位置,那么命令执行结束的时候描述符会自动还原。正如上面的ls /boot 2>&1 >/tmp/fff.log命令,在ls执行结束后,fd=2还原回默认的/dev/stderr,fd=1还原回默认的/dev/stdout。
但是我们可以通过exec程序直接在当前的shell环境下改变重定向目标,只有在当前shell退出的时候才会释放描述符的绑定。
Tips:shell的内建命令exec将并不启动新的shell,而是用要被执行命令替换当前的shell进程,并且将老进程的环境清理掉,而且exec命令后的其它命令将不再执行。
不过,要注意一个例外,当exec命令来对文件描述符操作的时候,就不会替换shell,而且操作完成后,还会继续执行接下来的命令。
例如:下面的命令将标准错误fd=2指向fd=3对应的文件上。
┌──(root💀kali)-[~]
└─# exec 2>&3 1 ⨯
因此,我们可能在一段程序执行结束后,需要将描述符还原到原来的位置,并关闭不再需要的描述符。毕竟描述符也是资源,是有限的(ulimit -n)。
6. 关闭文件描述符
[n]>&-
-
[n]<&-
关闭文件描述符的方式是将[n]>&word
和[n]<&word
中的word使用符号”-“,这表示释放fd=n描述符,且关闭其指向的文件。
7. 打开文件
[n]<> filename
:打开filename,并指定其文件描述符为n,该描述符是可读、可写的描述符。若不指定n则默认为0,若filename文件不存在,则先创建filename文件。
8. 文件描述符的移动
文件描述符的移动表示将文件描述符1移动到描述符2上,同时关闭文件描述符1。
[n]>&digit-
:将文件描述符digit代表的输出文件移动到n上,并关闭digit值的描述符。
[n]<&digit-
:将文件描述符digit代表的输入文件移动到n上,并关闭digit值的描述符。