如何设置shell脚本的进程组?另外我希望所有子进程都在同一个进程组中
我期望类似于 C 中的 setpgid() 的东西。
正如PSkocik 指出的那样,在大多数 shell 中,可以通过激活作业控制(“监视模式”)在自己的进程组中运行进程。
(set -m; exec process_in_its_own_group)
Linux 有一个setsid
实用程序,它运行在它自己的会话中作为参数传递的命令(使用同名的系统调用)。这比在自己的进程组à la中运行它要强setpgrp
,但这可能适合您的目的。
如果您想将进程放在现有组中而不是在其自己的组中(即,如果您想要 的全部功能setpgid
),则没有通用的 shell 实用程序。你必须使用 C/ Perl/...</ p>
我将回答我理解的部分内容:
如何强制当前的 bash shell 脚本成为自进程组:
我把它放在我的 bash 脚本的开头:
pgid_from_pid() {
local pid=$1
ps -o pgid= "$pid" 2>/dev/null | egrep -o "[0-9]+"
}
pid="$$"
if [ "$pid" != "$(pgid_from_pid $pid)" ]; then
exec setsid "$(readlink -f "$0")" "$@"
fi
为什么我需要这个?
从交互式bash 会话启动程序时,它会获得自己的新进程组。但是,如果您的程序是从 bash 脚本(非交互式)调用的,则情况并非如此。如果您的程序在这两种情况下都依赖于进程组所有者,那么您将需要它。
我不认为 Bourne、bash 或 zsh 会让你这样做,但你可以在 perl 中使用内置的来做到这一点setpgrp
(注意与 POSIX 的名称略有不同)。传递零作为 PID 来修改 perl 进程本身的组:
setpgrp(0, 12345) || die "$!"
您可能认为您可以使用来自 bash 的 perl 来设置 bash 进程的组($$
例如,通过传递给 perl 脚本),但我认为 perl 进程不能修改进程组它没有分叉。
根据您要执行的操作,各种 shell 中的作业控制功能可能会以不同的方式为您提供所需的内容,就像您只想从终端分离一样。
更新:我认为奇怪的是,这个答案在没有明确解释原因的情况下收到了几票否决票。我的猜测是,downvoters 误解了这个问题,即询问如何更改当前shell的进程组。或者也许他们知道如何从 shell 执行 setpgrp,但对自己保密。
如果打开set -m
,新进程将在新进程组中生成,如果它们是后台的,它们将不会忽略 SIGINT 和 SIGQUIT。
if [ $$ = $(ps -o pgid -hp $$) ]; then
echo already a process group leader;
else
set -m
$0 "$@" #optionally with &
set +m
fi
之后运行的程序的新进程组set -m
将作为终端的前台进程组,除非它们在后台运行。
如果实现支持“用户可移植性实用程序” ,set -m
则显然是 POSIX 要求的半标准。在实践中,它适用于bash
, dash
, ksh
, pdksh
, sh
, yash
, 和zsh
. posh
没有。
这是一个较晚的综合,取自这里的其他几个很好的答案,如果您的意图是清理任何产生的子shell进程(即使脚本本身不是直接从交互式shell启动,而是从另一个进程启动,因此不会自动成为它自己的进程组负责人),如有必要,重新启动当前脚本作为新的进程组负责人。
# First, obtain the current PGID, by parsing the output of "ps".
pgid=$(($(ps -o pgid= -p "$$")))
# Check if we're already the process group leader; if not, re-launch ourselves.
# Use setsid instead of set -m (...) to avoid having another subshell in between. This helps that the trap gets executed when the script is killed.
[ $$ -eq $pgid ] || exec setsid --wait "${BASH_SOURCE[0]}" "$@"
# Kill any subshell processes when the script exits.
trap "kill -- -$pgid" EXIT
# Note: If the script only starts background jobs, and that's all you care about, you can replace all of the above with this simple trap:
#trap "jobs -p | xargs kill --" EXIT # Kill remaining jobs when the script exits.
当一个执行子shell 清理的脚本被另一个这样的脚本调用时,会引入另一种复杂情况。进程组领导不嵌套;一旦一个脚本承担了领导权,它的生命周期就不再受父脚本的控制,所以当父脚本被中断或杀死时,嵌套脚本将继续存在。这不是用户通常想要的。
以下脚本片段使用合作模型扩展了上述实现,因此只有顶级脚本承担进程组领导,并通过导出$PGID
. 如果一个 subshell 找到一个现有的领导者,它不会自己承担领导权,并将自己的清理任务限制在剩余的工作中。只有在顶级脚本退出后,其他子 shell 才会被杀死。(因此,当一个脚本只调用一个或几个其他脚本时,这种合作模式效果最好。)
if [ -z "$PGID" ]; then # No parent script has become the process group leader yet.
pgid=$(($(ps -o pgid= -p "$$"))) # By defining this, we'll be killing subshell processes of this process group when we're done or interrupted. Any children with the same ambition will defer to us.
if [ $$ -eq $pgid ]; then
export PGID=$pgid # We are (already / after setsid) in our own process group, announce our leadership to any children, so that they don't become leaders themselves and thereby decouple themselves from our lifetime control.
else
exec setsid --wait "${BASH_SOURCE[0]}" "$@" # Use setsid instead of set -m (...) to avoid having another subshell in between.
fi
fi
if [ -n "$pgid" ]; then
trap "kill -- -$pgid" EXIT # If we're the leader, kill subshell processes when the script exits.
else
trap "jobs -p | xargs kill --" EXIT # Someone else is the leader; killing remaining jobs is all we can do here.
fi
正如@Rob Davis 在他的回答中指出的那样,设置进程组并不是你想要的 shell。
相反,您想使用他们的过程控制机制。这个答案涵盖了sh
在linux和borne上执行此操作。简而言之:
#! /bin/sh
# Kill all opened jobs on exit.
trap 'kill $(jobs -p)' EXIT
这将杀死在后台打开的所有作业(例如使用&
)。