Bash 4.4.19(1)-发布
我下面有一个简单的脚本,它是日志记录应用程序的基础。由于各种原因,我不得不使用进程替换。
这runner
是应用程序的核心,由于进程替换是异步的,因此我设法通过循环使其达到良好的连贯性while
。它工作完美。
不幸的是我发现了一个它不起作用的情况:当我执行 ' bash <filename> <function>
'
所以我们需要 2 个文件来重现。
要求:
- 为什么会发生这种情况?
- 如何修改我的 while 循环以适应类似的情况?
简化的脚本是:
test.sh
#!/bin/bash
2sub() {
local in=$(cat); echo -e "$in";
}
runner () {
"${@}" 1> >(2sub)
while [ -e /proc/$! ]; do sleep 0.1; done # <<< LOOP WAIT FOR $!
}
remotesub() {
bash ./test2.sh remotesub2
}
echo -e "running\n";
runner bash ./test2.sh remotesub2 # LOOPS
# runner remotesub # A POSSIBLE BYPASS/SOLUTION? But why?
echo -e "done!\n"
test2.sh
remotesub2() {
echo -e "'${BASH_VERSION}'"
return 0
}
"$@"
旁路:
bash <filename> <function>
正如您从脚本中看到的,通过包含在函数内部,可以绕过该问题,并且传递函数到runner
.为什么这是有效的而不是直接的方式,我相信这里有人知道。
请阐明这个问题,以及是否有一些更好的方法来执行等待循环以涵盖这些情况。
解决方案:
最好的解决方案是 mosvy 建议的。谢谢。使用 { "${@}"; }
消除了将命令打包在单独的小函数中的需要,这是一种痛苦。另外,在对较大的代码进行了多个小时的测试之后,我得出的结论是,仔细终止子进程使得这变得while [ -e /proc/$! ]; do sleep 0.1; done
不必要。该行被替换为wait $!;
答案1
如果我完全理解你的意思,你想知道为什么只有当它是内置命令或函数的命令行的一部分时$!
才会设置为内部运行的进程的PID >(...)
,而不是当它是内置命令或函数的命令行的一部分时一个外部命令。
简化示例:
$ bash -c 'true > >(echo in=$BASHPID; sleep .1); echo psubst=$!'
psubst=12392
in=12392
$ bash -c '/bin/true > >(echo in=$BASHPID; sleep .1); echo psubst=$!'
in=12751
psubst=
发生这种情况是因为在使用外部命令的情况下,bash
将分叉一个单独的进程来运行它,并且在其中运行的进程>(...)
将作为该进程的子进程运行,因此作为盛大您的脚本的子级,完全超出其控制范围。
当外部命令终止时,其子命令(如果仍在运行)将被 pid 1 (init) 采用,因此任何仍可用于从脚本中检索其 PID 的链接都会被破坏。
解决方法可能是使用包装函数,该函数将导致其命令行中的所有进程替换作为脚本的子进程运行,因此可以通过pgrep -P "$$"
.
此外,将外部命令放入{...}
块并重定向块的输出似乎也有效:
$ bash -c 'func(){ /bin/true; }; func > >(echo in=$BASHPID; sleep .1); echo psubst=$!'
in=3574
psubst=3574
$ bash -c '{ /bin/true; } > >(echo in=$BASHPID; sleep .1); echo psubst=$!'
in=3435
psubst=3435
两种解决方法都依赖于当前实现的工作方式;例如。bash
有一天可能会决定优化掉琐碎的组命令或功能,打破这些假设。
请注意,$!
设置为最后一个进程替换的 PID 是一项未记录的功能,该功能在bash
.