$!未设置为与外部命令一起使用的 > >(...) 进程替换的 PID

$!未设置为与外部命令一起使用的 > >(...) 进程替换的 PID

Bash 4.4.19(1)-发布

我下面有一个简单的脚本,它是日志记录应用程序的基础。由于各种原因,我不得不使用进程替换。

runner是应用程序的核心,由于进程替换是异步的,因此我设法通过循环使其达到良好的连贯性while。它工作完美。

不幸的是我发现了一个它不起作用的情况:当我执行 ' bash <filename> <function>'

所以我们需要 2 个文件来重现。

要求:

  1. 为什么会发生这种情况?
  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.

相关内容