Linux:八种重定向详解(万字长文警告)

日晨难再 2024-10-16 08:37:01 阅读 92

相关阅读Linux

icon-default.png?t=O83A

https://blog.csdn.net/weixin_45791458/category_12234591.html?spm=1001.2014.3001.5482


        本文将讨论Linux中的重定向相关问题,在阅读本文前,强烈建议先学习文件描述符的相关内容Linux:文件描述符详解。

         重定向分为两类:第一类是全局重定向,它对后续在Bash中创建的所有子进程都生效(因为文件描述符的继承);第二类是命令重定向,它只对单个命令生效。

全局重定向

输入重定向

<code>exec [number|varname]< filename

# 如果不使用exec,则无法影响当前Bash进程

# [number|varname]和<之间不能有空格

        输入重定向会首先将文件名进行拓展(如参数扩展和命令替换),如果使用number,会在文件描述符number上以读模式打开该文件(实际上,系统调用open打开一个文件返回的文件描述符是最小可用的整数值,这是不能直接控制的,但可以用系统调用dup、dup2或fcntl进行复制);如果使用varname,则会自动分配一个大于等于10的文件描述符并将其赋值给变量varname;如果未指定number或varname,则默认为标准输入(文件描述符0)。

        例1展示了指定数字分配文件描述符和自动分配文件描述符的过程。

例1

[zhangchen@EDA Desktop]$ ps

PID TTY TIME CMD

34005 pts/0 00:00:00 bash

34070 pts/0 00:00:00 ps

[zhangchen@EDA Desktop]$ ls -al /proc/34005/fd # 查询bash进程的文件描述符

total 0

dr-x------. 2 zhangchen zhangchen 0 Sep 24 14:08 .

dr-xr-xr-x. 9 zhangchen zhangchen 0 Sep 24 14:08 ..

lrwx------. 1 zhangchen zhangchen 64 Sep 24 14:08 0 -> /dev/pts/0

lrwx------. 1 zhangchen zhangchen 64 Sep 24 14:08 1 -> /dev/pts/0

lrwx------. 1 zhangchen zhangchen 64 Sep 24 14:08 2 -> /dev/pts/0

lrwx------. 1 zhangchen zhangchen 64 Sep 24 14:09 255 -> /dev/pts/0

[zhangchen@EDA Desktop]$ exec 5< test1 # 指定数字分配文件描述符

[zhangchen@EDA Desktop]$ ls -al /proc/34005/fd

total 0

dr-x------. 2 zhangchen zhangchen 0 Sep 24 14:08 .

dr-xr-xr-x. 9 zhangchen zhangchen 0 Sep 24 14:08 ..

lrwx------. 1 zhangchen zhangchen 64 Sep 24 14:08 0 -> /dev/pts/0

lrwx------. 1 zhangchen zhangchen 64 Sep 24 14:08 1 -> /dev/pts/0

lrwx------. 1 zhangchen zhangchen 64 Sep 24 14:08 2 -> /dev/pts/0

lrwx------. 1 zhangchen zhangchen 64 Sep 24 14:09 255 -> /dev/pts/0

l-wx------. 1 zhangchen zhangchen 64 Sep 24 14:08 5 -> /home/zhangchen/Desktop/test1

[zhangchen@EDA Desktop]$ exec {fd}< test2 # 自动分配文件描述符

[zhangchen@EDA Desktop]$ echo $fd

10

[zhangchen@EDA Desktop]$ ls -al /proc/34005/fd

total 0

dr-x------. 2 zhangchen zhangchen 0 Sep 24 14:08 .

dr-xr-xr-x. 9 zhangchen zhangchen 0 Sep 24 14:08 ..

lrwx------. 1 zhangchen zhangchen 64 Sep 24 14:08 0 -> /dev/pts/0

lrwx------. 1 zhangchen zhangchen 64 Sep 24 14:08 1 -> /dev/pts/0

l-wx------. 1 zhangchen zhangchen 64 Sep 24 14:08 10 -> /home/zhangchen/Desktop/test2

lrwx------. 1 zhangchen zhangchen 64 Sep 24 14:08 2 -> /dev/pts/0

lrwx------. 1 zhangchen zhangchen 64 Sep 24 14:09 255 -> /dev/pts/0

l-wx------. 1 zhangchen zhangchen 64 Sep 24 14:08 5 -> /home/zhangchen/Desktop/test1

        例2展示了输入重定向过程中的系统调用,由于strace无法直接在交互式bash中追踪内建命令exec,我们选择编写一个测试脚本。

# 例2

# 文件:redirection.sh

exec 5< test1

exec {fd}< test2

[zhangchen@EDA Desktop]$ strace bash redirection.sh

*****

open("test1", O_RDONLY) = 3

fcntl(5, F_GETFD) = -1 EBADF (Bad file descriptor)

dup2(3, 5) = 5

close(3) = 0

rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0

open("test2", O_RDONLY) = 3

fcntl(3, F_DUPFD, 10) = 10

close(3)

*****

        可以看出,在系统调用open打开文件test1和test2时,返回的文件描述符都是其实3,但是系统调用dup2(3, 5)和fcntl(3, F_DUPFD, 10)将其复制为了文件描述符5和10。系统调用open的参数O_RDONLY表示以只读模式打开。

        如果在交互式bash中,将标准输入重定向为了其他文件,则终端会直接退出,如例3所示。

# 例3

[zhangchen@EDA Desktop]$ exec 0< test # 终端会直接退出

[zhangchen@EDA Desktop]$ exec 0< test; sleep 5 # 终端会在等待5秒后退出

        下面的命令可以关闭已打开的文件描述符,如例4所示。

exec [number|varname]<& -

exec [number|varname]>& -

# 两种命令效果是一样的,并不区分输入还是输出

# 它们唯一的区别在于,不指定number或varname时

# <&-默认指的是关闭标准输入描述符,即0<&-;>&-默认指的是关闭标准输出描述符,即1>&-

# [number|varname]和<、>和&之间不能有空格,和-间可以有

# 例4

[zhangchen@EDA Desktop]$ exec 5<& - # 关闭文件描述符5

[zhangchen@EDA Desktop]$ exec {fd}>& - # fd的值为10,因此关闭文件描述符10

输出重定向

exec [number|varname]>[|] filename

# 如果不使用exec,则无法影响当前Bash进程

# [number|varname]和>h和[|]之间不能有空格

        输出重定向会首先将文件名进行拓展(如参数扩展和命令替换),如果使用number,会在文件描述符number上以写模式打开该文件,如果文件不存在则会创建它;如果使用varname,则会自动分配一个大于等于10的文件描述符并将其数字赋值给变量varname;如果未指定number或varname,则默认为标准输出(文件描述符1)。

        如果重定向操作符是>,并且启用了noclobber选项(通过命令set -o noclobber启用),则如果目标文件已经存在且是常规文件,则重定向会失败。

        如果重定向操作符是>|,或者使用>但未启用noclobber选项,则如果目标文件已经存在,会将其截断为零字节。

        例5展示了输出重定向过程中的系统调用,由于strace无法直接在交互式bash中追踪内建命令exec,我们选择编写一个测试脚本。

# 例5

# 文件:redirection.sh

set -o noclobber

exec 5> test1 # 重定向失败

exec {fd}>| test2

[zhangchen@EDA Desktop]$ strace bash redirection.sh

*****

write(2, "redirection.sh: line 2: test1: c"..., 62redirection.sh: line 2: test1: cannot overwrite existing file

) = 62

open("test2", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

fcntl(3, F_DUPFD, 10) = 10

close(3)

*****

        系统调用open的参数O_WDONLY表示以只写模式打开,参数O_CREAT表示如果文件不存在,则创建它,参数O_TRUNC表示如果文件已存在,则将其内容截断为零字节。

        例6展示了输出重定向后,后续命令的输出都不出现在终端。

# 例6

[zhangchen@EDA Desktop]$ exec > test1 # 标准输出(文件描述符1)重定向到文件test1

[zhangchen@EDA Desktop]$ echo 123456789 # 向标准输出(文件描述符1)输出文本

[zhangchen@EDA Desktop]$ exec >&- # 关闭标准输出(文件描述符1)

[zhangchen@EDA Desktop]$ cat test1 # 向标准输出(文件描述符1)输出文本

cat: standard output: Bad file descriptor # 标准输出(文件描述符1)已被关闭

[zhangchen@EDA Desktop]$ exec > /dev/pts/0 # 重新让标准输出(文件描述符1)指向终端

[zhangchen@EDA Desktop]$ cat test1 # 向标准输出(文件描述符1)输出文本

123456789 # 成功输出文本

        例6中还存在一个插曲,当将标准输出重定向到test1并写入文本12345678后,使用exec >&-关闭文件描述符,此时如果尝试使用cat输出test1文件的内容会报错,这是因为此时标准输出,即文件描述符0并未指向有效的终端,因此需要将标准输出重定向回终端,此时才能正常显示。

输出追加重定向

exec [number|varname]>> filename

# 如果不使用exec,则无法影响当前Bash进程

# [number|varname]和>>之间不能有空格

        与输出重定向类似,不同的是,如果文件已经存在,则新的输出将附加到文件末尾,而不会清空原有内容。

        例7展示了输出追加重定向过程中的系统调用,由于strace无法直接在交互式bash中追踪内建命令exec,我们选择编写一个测试脚本。

# 例7

# 文件:redirection.sh

exec 5>> test1

[zhangchen@EDA Desktop]$ strace bash redirection.sh

*****

open("test1", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3

fcntl(5, F_GETFD) = -1 EBADF (Bad file descriptor)

dup2(3, 5) = 5

close(3) = 0

rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0

*****

        系统调用open的参数O_WDONLY表示以只写模式打开,参数O_CREAT表示如果文件不存在,则创建它,参数O_APPEND表示在写入数据时,所有数据都会追加到文件的末尾,而不是从文件开头开始写入。

        例8展示了输出追加重定向后,输出的内容是追加到文件末尾的。

# 例8

[zhangchen@EDA Desktop]$ cat test1

123456789

[zhangchen@EDA Desktop]$ exec >> test1 # 标准输出(文件描述符1)重定向到文件test1

[zhangchen@EDA Desktop]$ echo 123456789 # 向标准输出(文件描述符1)输出文本

[zhangchen@EDA Desktop]$ exec >&- # 关闭标准输出(文件描述符1)

[zhangchen@EDA Desktop]$ exec > /dev/pts/0 # 重新让标准输出(文件描述符1)指向终端

[zhangchen@EDA Desktop]$ cat test1 # 向标准输出(文件描述符1)输出文本

123456789 # 追加输出

123456789

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

exec &> filename # 更好的格式

exec >& filename

# 如果不使用exec,则无法影响当前Bash进程

# &和>之间不能有空格

        标准输出和标准错误重定向会首先将文件名进行拓展(如参数扩展和命令替换),然后将标准输出(文件描述符1)和标准错误输出(文件描述符2)都重定向到同一个文件。

        需要注意的是,使用>&时文件不能扩展为number或-,如果是,则会被当作是复制文件描述符(见后),这是为了兼容性考虑。 

        例9展示了标准输出和标准错误重定向过程中的系统调用,由于strace无法直接在交互式bash中追踪内建命令exec,我们选择编写一个测试脚本。

# 例9

# 文件:redirection.sh

exec &> test1

[zhangchen@EDA Desktop]$ strace bash redirection.sh

*****

open("test1", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

dup2(3, 1) = 1

close(3) = 0

dup2(1, 2) = 2

*****

        在系统调用open打开文件test1时,返回的文件描述符是3,而dup2(3, 1)将其复制为了文件描述符1,进一步dup2(1, 2)将文件描述符1复制为了文件描述符2,此时文件描述符1和2都指向test1文件。

        例10展示了标准输出和标准错误重定向后,后续命令的输出不出现在终端。

# 例10

# 文件:redirection.sh

exec &> test1

echo "This is standard output" # 标准输出

ls non_existent_file # 这将导致错误,产生标准错误

echo "This will also be standard output" # 另一个标准输出

[zhangchen@EDA Desktop]$ bash redirection.sh

[zhangchen@EDA Desktop]$ cat test

This is standard output

ls: cannot access non_existent_file: No such file or directory

This will also be standard output

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

exec &>> filename

# 如果不使用exec,则无法影响当前Bash进程

# &和>>之间不能有空格

        与标准输出和标准错误重定向类似,不同的是,如果文件已经存在,则新的输出将附加到文件末尾,而不会清空原有内容。 

        例11展示了标准输出和标准错误追加重定向过程中的系统调用,由于strace无法直接在交互式bash中追踪内建命令exec,我们选择编写一个测试脚本。 

# 例11

# 文件:redirection.sh

exec &>> test1

[zhangchen@EDA Desktop]$ strace bash redirection.sh

*****

open("test1", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3

dup2(3, 1) = 1

close(3) = 0

dup2(1, 2) = 2

*****

        在系统调用open打开文件test1时,返回的文件描述符是3,而dup2(3, 1)将其复制为了文件描述符1,进一步dup2(1, 2)将文件描述符1复制为了文件描述符2,此时文件描述符1和2都指向test1文件。 

        例12展示了标准输出和标准错误追加重定向后,输出的内容是追加到文件末尾的。 

# 例12

# 文件:redirection.sh

exec &> test1

echo "This is standard output" # 标准输出

ls non_existent_file # 这将导致错误,产生标准错误

echo "This will also be standard output" # 另一个标准输出

[zhangchen@EDA Desktop]$ cat test

12345678

[zhangchen@EDA Desktop]$ bash redirection.sh

[zhangchen@EDA Desktop]$ cat test

12345678

This is standard output

ls: cannot access non_existent_file: No such file or directory

This will also be standard output

复制文件标识符

exec [number|varname]<& filename

exec [number|varname]>& filename

# 两种命令效果是一样的,并不区分输入还是输出,,但建议与被复制(关闭)的文件描述符方向一致

# 它们唯一的区别在于,不指定number或varname时

# <& filename默认指的是被复制(关闭)的标准输入描述符,即0<& filename;>& filename默认指的是被复制(关闭)的标准输出描述符,即1>& filename

# [number|varname]和<、>和&之间不能有空格

如果filename展开为数字,则其表示要被复制的文件描述符,那么文件描述符number(指定)或varname(自动分配)将成为该文件描述符的副本,即指向同一个文件。如果filename展开为-,则其表示要关闭文件描述符。如果filename展开文件名,则其表示标准输出和标准错误重定向。其他情况下,会出现错误。

        例13展示了复制文件标识符过程中的系统调用,由于strace无法直接在交互式bash中追踪内建命令exec,我们选择编写一个测试脚本。 

# 例13

# 文件:redirection.sh

exec 3> test1

exec 4>& 3

[zhangchen@EDA Desktop]$ strace bash redirection.sh

*****

open("test1", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0

fcntl(4, F_GETFD) = -1 EBADF (Bad file descriptor)

dup2(3, 4) = 4

*****

        在系统调用open打开文件test1时,返回的文件描述符是3,此时不再需要系统调用dup2调整了,因为这就是我们指定的数字,而dup2(3, 4)将其复制为了文件描述符4,此时文件描述符3和4都指向test1文件。

移动文件描述符

exec [number|varname]<& number-

exec [number|varname]>& number-

# 两种命令效果是一样的,并不区分输入还是输出,但建议与被移动的文件描述符方向一致

# 它们唯一的区别在于,不指定number或varname时

# <& number-默认指的是移动到标准输入描述符,即0<& number-;>& number-默认指的是标准输出描述符,即1>& number-

# [number|varname]和<、>和&之间不能有空格,number和-之间不能有空格,&与和number之间可以有空格

        该命令将文件描述符number复制为文件描述符number(指定)或varname(自动分配),并在此后关闭文件描述符number。

        例14展示了移动文件描述符过程中的系统调用,由于strace无法直接在交互式bash中追踪内建命令exec,我们选择编写一个测试脚本。 

# 例14

# 文件:redirection.sh

exec 3> test1

exec 4>& 3-

[zhangchen@EDA Desktop]$ strace bash redirection.sh

*****

open("test1", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0

fcntl(4, F_GETFD) = -1 EBADF (Bad file descriptor)

fcntl(4, F_GETFD) = -1 EBADF (Bad file descriptor)

dup2(3, 4) = 4

fcntl(3, F_GETFD) = 0

close(3) = 0

*****

        可以看到,在系统调用dup2将文件描述符3赋值为了文件描述符4后,紧接着就是用系统调用close关闭了文件描述符3。

输入和输出重定向

exec [number|varname]<> filename

# 如果不使用exec,则无法影响当前Bash进程

# [number|varname]和<和>之间不能有空格

        以读写方式打开一个文件,是用文件描述符number(指定)或varname(自动分配),如果未指定number或varname,则默认为标准输入(文件描述符0)。 

        例15展示了移动文件描述符过程中的系统调用,由于strace无法直接在交互式bash中追踪内建命令exec,我们选择编写一个测试脚本。  

# 例15

# 文件:redirection.sh

exec 3<> test1

[zhangchen@EDA Desktop]$ strace bash redirection.sh

*****

open("test1", O_RDWR|O_CREAT, 0666) = 3

*****

        系统调用open的参数O_RDWR表示以读写模式打开,参数O_CREAT表示如果文件不存在,则创建它。

叠加使用

        上面的八种重定向命令,可以多个混合使用,此时从左往右依次执行,如下所示。

exec &> filename 等价于 exec > filename 2>& 1或exec > filename 2<& 1(不建议)

exec &>> filename 等价于 exec > filename 2>>& 1或exec > filename 2<<& 1(不建议)

命令重定向

        全局重定向的影响范围很广,所有后续的命令都会受到影响(因为文件描述符继承),但命令重定向只针对一条命令而言,重定向只发生在子进程中,其他都是相同的。

        下面给了一个例子,将echo命令的标准输出(文件描述符1)重定向至文件test1。

[zhangchen@EDA Desktop]$ echo test_message > test1

[zhangchen@EDA Desktop]$ cat test1

test_message

        下面展示了命令重定向过程中的系统调用情况。

# 文件:redirection.sh

ls > test1

echo 111111111111111111111

[zhangchen@EDA Desktop]$ strace -f bash redirection.sh # -f选项很重要,加上它才能追踪子进程的系统调用

*****

[pid 71192] open("test1", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

[pid 71192] dup2(3, 1) = 1

[pid 71192] close(3) = 0

[pid 71192] execve("/usr/bin/ls", ["ls"], 0x12f4bb0 /* 66 vars */) = 0

*****

write(1, "111111111111111111111\n", 22111111111111111111111 # 后面一串是命令的输出

) = 22

*****

        可以看到,系统调用open是在子进程[pid 71192]中进行的,echo的输出使用原本的标准输出(文件描述符1),即还是原来的终端,所以在后面看到了命令的输出。

        细心的人可能会发现,echo命令并没有创建子进程,虽然echo命令不是内建命令,但它是直接在原bash进程中执行的,如果重定向它的标准输入输出,岂不是会影响全局?其实不会,下面举例说明。

# 文件:redirection.sh

echo 111111111111111111111 > test1

echo 222222222222222222222

[zhangchen@EDA Desktop]$ strace -f bash redirection.sh # -f选项很重要,加上它才能追踪子进程的系统调用

open("test1", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

fcntl(1, F_GETFD) = 0

fcntl(1, F_DUPFD, 10) = 10

fcntl(1, F_GETFD) = 0

fcntl(10, F_SETFD, FD_CLOEXEC) = 0

dup2(3, 1) = 1

close(3) = 0

fstat(1, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0

mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6e4f095000

write(1, "111111111111111111111\n", 22) = 22

dup2(10, 1) = 1

fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)

close(10) = 0

rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0

write(1, "222222222222222222222\n", 22222222222222222222222

) = 22

*****

        原来在使用dup2函数将标准输出(文件描述符1)指向test1前,先使用了fcntl(1, F_DUPFD, 10)将文件描述符1原本指向的终端保存了下来,第一个echo执行后,又使用了dup2(10, 1) 将其还原!



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。