猫,回声和进程替换

4

我正在尝试理解这些命令之间的区别:

cat <<< yolo | tee f.txt
echo yolo | tee t.txt

并且这些命令:

cat <<< yolo > >(tee f.txt)
echo yolo > >(tee t.txt)

前两个命令的结果完全相同:打印出“yolo”,然后终端返回控制,这正是我所期望的。
``` [user@localhost ~]$ cat <<< yolo | tee f.txt yolo [user@localhost ~]$ echo yolo | tee t.txt yolo ```
但是,使用进程替换时,echo会出现一些奇怪的情况。
``` [user@localhost ~]$ cat <<< yolo > >(tee f.txt) yolo [user@localhost ~]$ echo yolo > >(tee t.txt) [user@localhost ~]$ yolo ```
在文本被打印出来之前,终端会立即返回控制。为什么在这种情况下我会更早地获得控制权呢?
这一定与如何打开进程以及如何在进程之间传递文件描述符有关,但我已经到达了我的知识极限。
如果我将其管道到其他任何东西,一切都恢复正常,例如:echo yolo > >(tee t.txt) | cat
更奇怪的是,将xargs应用于echo可以很好地工作:
``` [user@localhost ~]$ xargs echo <<< yolo > >(tee t.txt) yolo ```
但是你可以说主要程序是xargs,而不是echo
如果我在cat中使用输入进程替换,我会得到不同的结果:
``` cat < <(echo yolo) > >(tee t.txt) ```
有时它会给我这个:
``` [user@localhost ~]$ cat < <(echo yolo) > >(tee t.txt) [user@localhost ~]$ yolo ```
有时是这个:
``` [user@localhost ~]$ cat < <(echo yolo) > >(tee t.txt) yolo ```
因此,我猜这可能与系统执行命令的速度有关,这使得它变得不可预测。
那是否意味着输出进程替换(例如本例中的tee)在后台运行?

这似乎与echo是内置的事实有关,而在所有四个选项中,少了一个fork()/clone(),您可以将其替换为/bin/echo yolo > >(tee t.txt),然后您应该再次获得与前三个相同的行为。至少这是我观察到的。 - Ondrej K.
无论我是调用/usr/bin/echo yolo > >(tee t.txt)还是使用带引号的'echo' yolo > >(tee t.txt),都会得到与echo yolo > >(tee t.txt)相同的结果。但如果这与性能有关,则调用/usr/bin/echo可能会稍微慢/快一些,具体取决于系统,从而导致在不同计算机上出现/消失问题。 - vdavid
专业提示:您可以通过将进程替换的输出管道传递到另一个进程来同步它,这样另一个进程将不会退出,直到输入流打开并且bash等待它,因此它将有效地等待进程替换。 因此,echo yolo >(tee t.txt) | cat将同步输出。 - KamilCuk
为什么要避免使用PIPESTATUS?这段代码是可行的:{ echo yolo; echo $? > "temporary_tile"; } | tee t.txt; - KamilCuk
@KamilCuk 此外,我认为中间文件应该只在最后必要的情况下使用,因为文件系统存在很多危险(磁盘已满、写入权限等)。 - vdavid
显示剩余4条评论
1个回答

2
啊,我想我已经找到了...

进程替换 ... 进程list异步方式运行,其输入或输出出现为文件名...

一旦命令(或分叉进程)完成,控制将返回终端并显示下一个提示符。我最初怀疑内置的echo可能发挥了作用,但它确实只是扭曲了时间。也就是说,一旦使用> >(tee t.txt)时,tee何时打印到控制台并不完全确定。

至于这个问题,请尝试以下操作(针对您的第三个示例):

$ cat <<< yolo > >(sleep 1; tee f.txt)
$ yolo


与之相反:
$ { echo yolo; sleep 1 ;} > >(tee t.txt)
yolo
$

区别在于>(list)进程替换是异步执行的。尝试使用前面的两个示例中的第一个,即使设置了较大的sleep值来模拟长时间运行的进程,它仍将挂起,而您可以继续使用您的shell(实际上,即使您终止它,它也会重新被父进程接管,但您仍然可以在进程列表中看到它;副作用是“由于命令替换而运行的命令忽略键盘生成的作业控制信号SIGTTIN、SIGTTOU和SIGTSTP。”-》即使失去终端,它们也不会被杀死,与下面的异步(&)执行示例不同)。
另一方面,管道:
如果管道没有异步执行(参见列表),则shell等待管道中所有命令完成执行。
Shell不会恢复控制(下一个命令不会执行),直到管道中的所有命令都完成执行。
尝试:
$ echo yolo |  (sleep 1; tee f.txt)
yolo
$

与(类似于使用>(列表))相反:
echo yolo |  ((sleep 1; tee f.txt) &)
$ yolo

双子壳并不是真正需要的,我只是用它来在运行的 shell 中抑制作业控制消息。

很有趣,但我不太明白为什么tee会稍后终止。我以为退出catecho会像使用管道时一样关闭文件描述符。否则,这意味着由过程替代启动的进程可能会尝试从已关闭的管道中读取(更确切地说,在启动程序退出后不久关闭的管道)。 - vdavid
事件链是这样的:echo终止。 echo关闭stdout。 tee被通知输入已关闭。 tee终止。因此,由于我们有调度程序,bash进程可以在tee之前获得CPU时间,并且bash可以看到tee之前终止了echo。所以这就是你看到的 - bash获得足够的CPU时间来写入$,而tee稍后终止,刷新其stdout并写入yolo - KamilCuk
我认为 echo yolo | ((sleep 1; tee f.txt) &) 这个例子非常有启发性。我很清楚为什么这个命令会过早地返回控制权,实质上进程替换是(几乎)相同的。 - vdavid
我已经添加了一个部分来解释区别。文件/管道处理的机制大体相似(在>(list)的情况下,它会再次复制句柄,并且可以通过/dev/fd/访问->指向/proc/)。 - Ondrej K.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接