答案1
作为shell的子进程,ls
继承shell的打开文件描述符。标准文件描述符(stdin、stdout、stderr(或 0、1、2))连接到伪终端,由终端仿真器处理。
例如(在 Linux 系统上):
$ ls /proc/$$/fd -l
total 0
lrwx------ 1 muru muru 64 Dec 10 16:15 0 -> /dev/pts/3
lrwx------ 1 muru muru 64 Dec 10 16:15 1 -> /dev/pts/3
lrwx------ 1 muru muru 64 Dec 10 16:15 2 -> /dev/pts/3
lrwx------ 1 muru muru 64 Dec 10 16:15 255 -> /dev/pts/3
$ ls /proc/$(pgrep terminator -f)/fd -l | grep pts/3
lrwx------ 1 muru muru 64 Dec 10 16:15 26 -> /dev/pts/3
也就是说, 的输出ls
,或者 shell 本身,不是由 shell 处理的,而是由终端仿真器(GNOME Terminal、terminator、xterm 等)处理的。
你可以测试一下:
pts
在 Linux 上,找到终端仿真器(例如 GNOME 终端)使用的伪终端 ( ):
$ ls -l /proc/$(pgrep -n gnome-terminal)/fd | grep pts
lrwx------ 1 muru muru 64 Dec 10 18:00 1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Dec 10 18:00 15 -> /dev/pts/20
lrwx------ 1 muru muru 64 Dec 10 18:00 2 -> /dev/pts/1
现在,它会使用 的非标准 fd(0、1、2 以外的 fd)gnome-terminal
为 shell 提供输入和输出。终端仿真器读取发送到该 PTS 的数据,并(经过一些处理,例如颜色等)将其显示在屏幕上。在本例中,即15
, 连接到pts/20
。如果我向该点写一些东西,我可以期望它出现在该终端中:
进一步阅读:
另一种情况,我做的事情如下:
echo $(ls)
a=$(date)
vim `command -v some_script`
叫做命令替换。在命令替换中,命令的输出由 shell 本身捕获,并且永远不会到达终端,除非您将其打印出来(例如,echo $(ls)
)。此案处理于豪克·拉金的回答。
答案2
事实证明,我在命令替换的意义上误解了这个问题。只有在这种情况下,shell 才会参与输出处理。
希望这也引起人们的兴趣...
strace -p 2140
在我运行echo $(/bin/echo foo)
这个ll之前,我附加到一个shell 。这是结果的一部分:
pipe([3, 4])
pipe([5, 6])
...
read(3, "foo\n", 128)
这是子进程中发生的情况:
dup2(4, 1)
close(4)
close(3)
...
execve("/bin/echo", ...
shell 连接文件描述符 3 和 4,然后分叉。子进程stdout
在运行新程序之前将 fd 设为 4。因此,子进程写入的所有内容都stdout
可以由父 shell 在其 fd 3 上读取。