Bash进程替换和同步

l0b*_*0b0 18 bash process-substitution

(可能与某些程序有关,不接受输入文件的进程替换?)

在一些Bash单元测试脚本中,我使用以下技巧来记录显示命令的stdout和stderr:

command > >(tee "${stdoutF}") 2> >(tee "${stderrF}" >&2)
Run Code Online (Sandbox Code Playgroud)

此过程会向stdout生成一些输出,因此$stdoutF文件会获取一些数据.然后我运行另一个不输出任何数据的命令:

diff -r "$source" "$target" > >(tee "${stdoutF}") 2> >(tee "${stderrF}" >&2)
Run Code Online (Sandbox Code Playgroud)

但是,在运行空白测试(使用shunit-ng)之前,此过程看起来并不总是成功完成:

assertNull 'Unexpected output to stdout' "$(<"$stdoutF")"
Run Code Online (Sandbox Code Playgroud)

在100次运行测试中,这次失败了25次.

sync在测试文件空虚之前调用它是否足够:

sync
assertNull 'Unexpected output to stdout' "$(<"$stdoutF")"
Run Code Online (Sandbox Code Playgroud)

...和/或它应该通过强制执行命令的顺序来工作:

diff -r "$source" "$target" \
> >(tee "${stdoutF}"; assertNull 'Unexpected output to stdout' "$(<"$stdoutF")")
2> >(tee "${stderrF}" >&2)
Run Code Online (Sandbox Code Playgroud)

...和/或它可能以tee某种方式assertNull直接而不是文件?

更新:sync不是答案 - 请参阅下面的Gilles回复.

更新2:进一步讨论同步保存stdout,stderr和stdout + stderr.谢谢你的回答!

Gil*_*il' 27

在bash中,进程替换替换命令在foo > >(bar)完成后立即foo结束.(文档中未对此进行讨论.)您可以查看此内容

: > >(sleep 1; echo a)
Run Code Online (Sandbox Code Playgroud)

此命令立即返回,然后在a一秒后异步打印.

在您的情况下,该tee命令只需要一点点时间就可以在完成后command完成.添加sync给了tee足够的时间来完成,但这并没有消除竞争条件,只是添加一个sleep遗嘱,它只是使竞争更不可能显现.

更一般地说,sync没有任何内部可观察到的影响:如果您想访问文件系统存储在不同操作系统实例下的设备,它只会产生影响.更清楚地说,如果您的系统断电,只有sync在重新启动后才能保证在最后一次之前写入的数据可用.

至于消除竞争条件,这里有一些可能的方法:

  • 明确同步所有替换过程.

    mkfifo sync.pipe
    command > >(tee -- "$stdoutF"; echo >sync.pipe)
           2> >(tee -- "$stderrF"; echo >sync.pipe)
    read line < sync.pipe; read line < sync.pipe
    
    Run Code Online (Sandbox Code Playgroud)
  • 每一个命令,而不是重用使用不同的临时文件名$stdoutF$stderrF,并执行该临时文件始终是新创建的.

  • 放弃进程替换并改用管道.

    { { command | tee -- "$stdoutF" 1>&3; } 2>&1 \
                | tee -- "$stderrF" 1>&2; } 3>&1
    
    Run Code Online (Sandbox Code Playgroud)

    如果你需要命令的返回状态,bash会把它放入${PIPESTATUS[0]}.

    { { command | tee -- "$stdoutF" 1>&3; exit ${PIPESTATUS[0]}; } 2>&1 \
                | tee -- "$stderrF" 1>&2; } 3>&1
    if [ ${PIPESTATUS[0]} -ne 0 ]; then echo command failed; fi
    
    Run Code Online (Sandbox Code Playgroud)