我想看到twice
被输出两次,但这个脚本只会输出一次:
dump() {
(sleep 1; cat) > "$1"
}
(sleep 0; echo "twice") | tee >(dump "./a.txt")
echo "$(< "a.txt")"
要看两次,我必须调整睡眠时间:
dump() {
(sleep 0; cat) > "$1"
}
(sleep 1; echo "twice") | tee >(dump "./a.txt")
echo "$(< "a.txt")"
是什么导致了这里的比赛条件?
进程替换中的
dump
调用作为异步进程运行。这意味着tee
将其输出写入它,然后管道完成。管道完成是因为来自的输出tee
被缓冲了;如果您写入的数据多于管道缓冲区的大小,则必须tee
等待dump
使用它,并且您的原始代码很可能会起作用。假设您只写入少量数据,就像您在问题中所做的那样,然后您
a.txt
在管道终止后立即读取,然后dump
才有机会将任何内容写入文件(它仍在后台休眠,数据挂起在管道缓冲区中)。如果您在运行错误代码后查看该
a.txt
文件,您会注意到它包含字符串twice
。因此,sleep 1
在函数提供的轻微延迟之后,它最终会到达那里。要阻止管道过早终止,
cat
请在末尾添加:这使它工作,因为现在
cat
进程需要等待输出dump
通过管道到达(不会有,但它不知道)。这会延迟管道的终止,直到dump
调用返回。此时,数据已经被写入a.txt
并且可以被脚本中的最后一个命令获取。唯一同步管道中的进程的是 I/O,即从前一个进程读取数据并写入下一个进程。如果一个进程希望在某个时刻从上一步读取,它将阻塞,直到可以读取某些内容,或者直到上一步关闭了管道的末端。
cat
默认从标准输入读取。添加的标准输入cat
连接到管道的上一步中的标准输出tee
和进程替换dump
。该cat
实用程序将一直读取,直到没有其他内容可供读取。tee
直到两者dump
都完成执行后才会发生这种情况。代码的清理版本:
IIUC,问题是如何等待内部进程
>(...)
完成,然后再$(...)
从下一行执行命令替换。答案是没有好的方法可以做到这一点。如果您的系统支持该
/dev/fd/
机制,您可以使用一个exec fd> >(...)
技巧:是的,这很丑陋,但你可以做得更糟:
正如可以为此收集的那样,a)使用较新版本的 bash(> = 5.0),您可以
wait
让进程在内部运行,>(...)
并且b)这些进程可能不会终止,直到它们在其标准输入上获得 EOF,直到您关闭管道的另一端 -/dev/fd/63
或类似的>(...)
已扩展到。后者很难正确处理。某些版本的 Bash 有一个错误,即它们无法正确等待由进程替换产生的进程。