我有一个主 shell 文件main.sh
,以及许多后台子 shell 文件,它们都由执行调用。这些文件是a.sh
、b.sh
、c.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 &
这是因为您需要满足两个目标:
- 您需要先分叉脚本以了解其 PID,
- 您需要在分叉之前重定向脚本输出。
协进程基本上是一个./a.sh &
但输入和输出重定向到匿名管道。管道 fds 放置在${COPROC[@]}
阵列中。如果没有足够新的 bash,可以改用命名管道。
这里做什么:
- 在协进程中启动脚本。其输出将排队到尚未使用的管道。
- 从
${!}
获取分叉的 PID。 - 设置陷阱以在读取器终止时终止生成的脚本。
- 启动一个新的 sed 进程,该进程从 fd
${COPROC[0]}
处的管道读取脚本输出,并将 pid 附加到该进程。
请注意,sed
本身会阻止脚本,因此您也需要分叉它。而且由于它需要访问管道,因此您无法单独分叉它。相反,我们分叉整个函数。
陷阱可确保在终止生成的函数时终止生成的脚本。多亏了这一点,您可以执行以下操作:
prepend_pid ./a.sh &
spid=$!
sleep 4
kill ${spid}
不用担心./a.sh
会在后台继续运行而没有输出。