如何在 shell 中后台进程的每个提示消息前面回显 PID



我有一个主 shell 文件main.sh,以及许多后台子 shell 文件,它们都由执行调用。这些文件是a.shb.shc.sh...

我主要调用这些子外壳文件,如下所示:

#main.sh
./a.sh &
./b.sh &
./c.sh &

在每个后台子外壳文件中,格式如下所示

#a.sh
echo "This is a process"
子壳几乎

相同,输出消息对于我的任务是必需的,但很难确定哪些消息对应于哪个子壳进程。我正在考虑为它们分配一个 PID,并将该 PID 附加到每个子外壳输出的消息中。这如下所示:

[123]This is a process
[234]This is a process
...

谢谢!

编辑 在 main.sh 中启用此功能而不是修改子壳进程会很棒,因为我只想看到它们进行调试。

你为什么不这样启动它们呢?

./a.sh | sed 's/^/[a] /' &
./b.sh | sed 's/^/[b] /' &
./c.sh | sed 's/^/[c] /' &

或者即使您真的想包含 PID:

./a.sh | sed "s/^/[$!] /" &
./b.sh | sed "s/^/[$!] /" &
./c.sh | sed "s/^/[$!] /" &

应该工作。

变量$$包含当前的PID。

echo "[$$]This is a process"

如果它不需要是实际的进程 ID,而只是一个唯一标识符,则可以设置一个已知的环境变量:

#main.sh
MY_ID=1 ./a.sh &
MY_ID=2 ./b.sh &
MY_ID=3 ./c.sh &
#a.sh
echo "[$MY_ID]This is a process"

bash 4 或更高版本中,您可以使用 $BASHPID 访问 shell 的进程 ID,以便您无需在启动每个后台进程时指定MY_ID

#a.sh
echo "[$BASHPID]This is a process"

如果要自动在输出的每一行前面加上标识符,则需要为echo定义一个包装器,而不是直接使用它:

my_echo () { echo "[$MY_ID]$*"; }
my_echo () { echo "[$BASHPID]$*"; }

您可以创建一个名为"echo"的 bash 包装器函数并将其导出,因此您的后台脚本使用以下覆盖版本:

ubuntu@ubuntu:~$猫 bgscript#!/bin/bash对于 {1..3} 中的 i;回显"i=$i";做ubuntu@ubuntu:~$ 函数 echo { 内置 echo "[$$]$@"; }ubuntu@ubuntu:~$ 导出 -f 回声ubuntu@ubuntu:~$ ./bgscript &[1] 7290ubuntu@ubuntu:~$ [7290]i=1[7290]i=2[7290]i=3[1]+ 完成 ./bgscriptubuntu@ubuntu:~$

你可以使用 bash 协进程实现这样的事情(需要 bash 4)。代码如下所示:

prepend_pid() {
    coproc "${@}"
    pid=${!}
    trap "kill ${pid}" EXIT
    sed -e "s:^:${pid} :" </dev/fd/${COPROC[0]}
}
prepend_pid ./a.sh &
prepend_pid ./b.sh &
prepend_pid ./c.sh &

这是因为您需要满足两个目标:

  1. 您需要先分叉脚本以了解其 PID,
  2. 您需要在分叉之前重定向脚本输出。

协进程基本上是一个./a.sh &但输入和输出重定向到匿名管道。管道 fds 放置在${COPROC[@]}阵列中。如果没有足够新的 bash,可以改用命名管道。

这里做什么:

  1. 在协进程中启动脚本。其输出将排队到尚未使用的管道。
  2. ${!} 获取分叉的 PID。
  3. 设置陷阱以在读取器终止时终止生成的脚本。
  4. 启动一个新的 sed 进程,该进程从 fd ${COPROC[0]} 处的管道读取脚本输出,并将 pid 附加到该进程。

请注意,sed本身会阻止脚本,因此您也需要分叉它。而且由于它需要访问管道,因此您无法单独分叉它。相反,我们分叉整个函数。

陷阱可确保在终止生成的函数时终止生成的脚本。多亏了这一点,您可以执行以下操作:

prepend_pid ./a.sh &
spid=$!
sleep 4
kill ${spid}

不用担心./a.sh会在后台继续运行而没有输出。

最新更新