关于重定向的一个疑惑

sh/bash/dash/ksh/zsh等Shell脚本
回复
头像
jiandan23
帖子: 86
注册时间: 2010-12-17 22:31
系统: Mint 19.2

关于重定向的一个疑惑

#1

帖子 jiandan23 » 2021-01-15 14:48

1. 第一个疑惑:
{ echo hello > a.txt; } >b.txt
上述命令执行后,发现hello会被输出到a.txt文件里。这种顺序在bash手册的哪里有说明吗?

2. 第二疑惑:
sleep 1m > /dev/null |& cat
上述命令执行后,通过lsof发现,sleep 1m的stdout和stderr都被重定向到了/dev/null:
sleep 6945 root 1w CHR 1,3 0t0 1028 /dev/null
sleep 6945 root 2w CHR 1,3 0t0 1028 /dev/null
这种该如何解释?
(我的本来目的是:只将前面一个命令的stderr通过管道连接到后一个命令的stdin)
头像
astolia
论坛版主
帖子: 6460
注册时间: 2008-09-18 13:11

Re: 关于重定向的一个疑惑

#2

帖子 astolia » 2021-01-16 18:42

这个问题需要学一下linux系统编程,了解重定向在linux system call层面是怎么做到的就很好解释了。

重定向用的是dup2或dup3,可以在manpage里看具体的说明
https://manpages.ubuntu.com/manpages/fo ... dup.2.html

我假定你已经有一定的C语言知识,并且能理解以下概念:stdout的文件号固定为1,stderr的文件号固定为2,一个打开的文件号对应一个底层文件,不同文件号对应的底层文件可能不同也可能相同,文件号和底层文件之间的对应关系是可以改变的。

将当前进程stdout重定向到stderr,在linux system call层面的实现方法是

代码: 全选

dup2(2, 1);
即把stderr文件号2对应的底层文件赋值给stdout文件号1。当进程往原来的stdout文件号1里输出数据,实际就输出到了stderr对应的底层文件中,这样就实现了重定向stdout到stderr。
而将当前进程stdout重定向到一个文件,在linux system call层面的实现方法是

代码: 全选

int fd=open("/path/to/file", ...);
dup2(fd, 1);
只需要先打开需要的文件获得一个文件号即可。

这仅仅是对当前进程,如果要重定向其他程序怎么办呢?很简单,dup2/dup3之后直接用exec系列函数执行其他程序就成,exec后会继承当前进程的已打开文件号。
比如想实现shell脚本

代码: 全选

/path/to/xxx >/tmp/a
就用

代码: 全选

int fd=open("/tmp/a", ...);
dup2(fd, 1);
execl("/path/to/xxx", NULL);

到这里就可以回答你第一个疑问了。从上面的例子可以看出,重定向需要在执行具体的命令之前就做好准备。{ ...; }作为一个复合命令也不例外。在执行{}中的内容之前,就必须要执行一次dup2(fd1, 1),其中fd1是打开b.txt返回的文件号。而到了执行里面的echo时,由于也有一个重定向,还会执行一次dup2(fd2, 1),fd2是打开a.txt返回的文件号。也就是说stdout的文件号1对应的底层文件先被设置成了fd1的b.txt,再被设置成了fd2的a.txt,最终结果就是hello输出到了a.txt里


第二个疑问,bash的manpage里在Pipelines一段明确写了,|&等同于2>&1 |。也就是说,你的

代码: 全选

sleep 1m > /dev/null |& cat

代码: 全选

sleep 1m > /dev/null 2>&1 | cat
是完全等价的。
那么这么一来,这段脚本管道之前部分的实现就是

代码: 全选

int fd=open("/dev/null", ...);
dup2(fd, 1);
dup2(1, 2);
execlp("sleep", "1m", NULL);
最终stdout和stderr对应的底层文件都被设置成了/dev/null

要想实现你的原意,方法其实也很简单,把两个重定向交换个位置就行:

代码: 全选

sleep 1m 2>&1 >/dev/null | cat
头像
jiandan23
帖子: 86
注册时间: 2010-12-17 22:31
系统: Mint 19.2

Re: 关于重定向的一个疑惑

#3

帖子 jiandan23 » 2021-01-18 10:58

感谢astolia的耐心回复,两个问题都已经搞明白了~ 谢谢!
(之前我还有个误解,以为在"cmd1 >/dev/null | cmd2"中,两个重定向的顺序,是先">/dev/null",然后再“|cmd2”,结果看了一遍手册,发现管道的重定向才是最先执行的)
回复