【问题标题】:Stdout race condition between script and subscript脚本和下标之间的标准输出竞争条件
【发布时间】:2014-09-21 10:01:41
【问题描述】:

我正在尝试调用一个脚本 deepScript 并在另一个脚本 shallowScript 中处理其输出;它看起来像下面的代码片段:

shallowScript.sh

#!/bin/zsh
exec 1> >( tr "[a-z]" "[A-Z]" )
print "Hello - this is shallowScript"
. ./deepScript.sh

deepScript.sh

#!/bin/zsh
print "Hello - this is deepScript"

现在,当我运行 ./shallowScript.sh 时,结果是不稳定的:要么按预期工作(很少),要么打印一个空行,后跟两个预期的行(有时),或者它打印两行然后挂起,直到我按回车并给它一个换行符(大部分时间)。 到目前为止,我发现了以下内容:

  • 这可能是一种竞争条件,因为两个“打印”试图同时输出到标准输出;在调用“../deepScript.sh”之前插入“sleep 1”可以始终如一地纠正问题
  • 问题来自进程替换“exec 1> >(tr ...)”;将其注释掉也可以始终如一地纠正问题

我浏览了很多关于进程替换和重定向的论坛和帖子,但无法找到如何保证我的脚本同步调用命令。想法?

zsh --version                                                                                                                                                                   
zsh 5.0.5 (x86_64-apple-darwin14.0)

[编辑]

由于这个策略似乎注定会失败或导致可怕的变通语法,这里是另一种似乎适用于可忍受语法的策略:我从 shallowScript.sh 中删除了所有重定向和创建了第三个脚本,其中输出处理发生在函数中:

shallowScript.sh

#!/bin/zsh
print "Hello - this is shallowScript"
. ./deepScript.sh

thirdScript.sh

#!/bin/zsh
function _process {
  while read input; do
    echo $input | tr "[a-z]" "[A-Z]"
  done
}
. ./shallowScript.sh | _process

【问题讨论】:

  • _process中的while循环是不必要的; _process () { tr "[a-z]" "[A-Z]"; }tr 将处理其标准输入的每一行,因此无需为函数的标准输入的每一行调用一次。
  • 真;我使用tr 制作了一个易于理解和可测试的代码sn-p,但我的真实脚本试图做的不仅仅是tr

标签: shell stdout zsh race-condition io-redirection


【解决方案1】:

我想问题是执行脚本后看不到提示:

$ ./shallowScript.sh
$ HELLO - THIS IS SHALLOWSCRIPT
HELLO - THIS IS DEEPSCRIPT
(nothing here)

并认为它挂在这里并等待换行符。实际上并没有,而且这种行为是非常令人期待的。

你可以输入任何 shell 命令而不是换行符,例如ls 会被执行。

$ ./shallowScript.sh
$ HELLO - THIS IS SHALLOWSCRIPT  <--- note the prompt in this line
HELLO - THIS IS DEEPSCRIPT
echo test                        <--- my input
test                             <--- its result
$

这里发生的是:第一个 shell(运行 shallowScript.sh 的那个)创建一个管道,执行一个 dup2 调用以将其 stdout (fd 1) 转发到创建管道的写入端,然后然后分叉一个新进程(tr),以便父打印到stdout 的所有内容都发送到trstdin

接下来发生的事情是主 shell(您键入初始命令 ./shallowScript.sh 的那个)不知道它应该延迟打印下一个命令提示符,直到 tr 进程结束。它对tr 一无所知,所以它只是等待shallowScript.sh 执行,然后打印一个提示。 tr 那时仍在运行,这就是为什么它的输出(两行)出现在提示符打印之后,并且您认为 shell 正在等待换行符。实际上不是,它已经为下一个命令做好了准备。您可以在脚本输出之前、内部或之后的某处看到打印的提示($ 字符或其他字符),这取决于tr 进程完成的速度。

每次您的进程分叉并且当父进程已经死亡时子进程继续写入其stdout 时,您都会看到这种行为。

长话短说,试试这个:

$ ./shallowScript.sh | cat
HELLO - THIS IS SHALLOWSCRIPT
HELLO - THIS IS DEEPSCRIPT
$

在这里,shell 将等待 cat 进程完成,然后再打印下一个提示符,并且 cat 将仅在其所有输入(例如来自 tr 的输出)被处理后完成,正如您所期望的那样.

更新:在这里找到了 zsh 文档中的相关引用:http://zsh.sourceforge.net/Doc/Release/Expansion.html#Process-Substitution

&gt;(process) 还有一个问题;当它附加到外部命令时,父 shell 不会等待进程完成,因此紧随其后的命令不能依赖于完成的结果。问题和解决方法与重定向中MULTIOS 部分中描述的相同。因此,在上面示例的简化版本中:

paste <(cut -f1 file1) <(cut -f3 file2) > >(process)

(注意不涉及MULTIOS),就父shell而言,进程将异步运行。解决方法是:

{ paste <(cut -f1 file1) <(cut -f3 file2) } > >(process)

在你的情况下,它会给出这样的结果:

{
        print "Hello - this is shallowScript"
        . ./deepScript.sh
} 1> >( tr "[a-z]" "[A-Z]" )

当然可以,但看起来比原来的差。

【讨论】:

  • 谢谢,这个解释很有道理。只是一个问题,我应该将“| cat”技巧视为解决方案还是转机?它似乎不容易概括,每次我从交互式 shell 调用脚本时都必须记住它,感觉有点尴尬。
  • 这绝对是一种解决方法,不能用于日常脚本编程。一般规则不应该是写出即使在父母去世后仍继续写作的孩子的脚本。在您的特定情况下,为什么不使用普通的| 重定向将输出传递给tr,例如./shallowScript.sh | tr "[a-z]" "[A-Z]"?
  • 好吧,因为在我的实际脚本中,我的下标的处理比简单的 re 要长得多,而且因为进程替换使语法非常优雅。
  • 用相关 zsh 文档的链接更新了答案。
  • ... &gt; &gt;( ... ) 只是一个简单的管道,相当于... | ...。只有在需要对文件进行写访问时才需要进程替换。与tee 一起使用很常见:... | tee &gt;( ... ) &gt;( ... ),其中tee 需要文件名作为参数。
猜你喜欢
  • 2019-10-17
  • 2016-07-26
  • 2017-04-19
  • 2015-05-02
  • 2016-02-02
  • 2013-11-14
  • 2016-11-16
  • 1970-01-01
  • 2015-12-04
相关资源
最近更新 更多