如何设置shell脚本的进程组

Jac*_*cob 30 linux shell process process-group

如何设置shell脚本的进程组?此外,我希望所有子进程都在同一进程组中

我期待类似于C中的setpgid().

Gil*_*il' 18

正如PSkocik指出的那样,通过激活作业控制("监控模式"),可以在大多数shell中在自己的进程组中运行进程.

(set -m; exec process_in_its_own_group)
Run Code Online (Sandbox Code Playgroud)

Linux有一个setsid实用程序,它在自己的会话中运行作为参数传递的命令(使用同名系统调用).这是不是在自己的运行它更强的进程组点菜setpgrp,但是这可能是确定你的目的.

如果您想将流程放在现有的组而不是它自己的组中(即如果您想要全部功能setpgid),则没有常见的shell实用程序.你必须使用C/Perl/...

  • `set -m` 需要一个可能不存在的 tty (4认同)

vaa*_*aab 11

我将回答我理解的部分内容:

如何强制当前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
Run Code Online (Sandbox Code Playgroud)

为什么我需要这个?

交互式 bash会话启动程序时,它会获得自己的新进程组.但是,如果从bash脚本(非交互式)调用程序,则情况并非如此.如果您的程序在两种情况下都依赖于进程组所有者,那么您将需要这样做.


Rob*_*vis 9

我不认为Bourne,bash或zsh会让你这样做,但是你可以使用内置的perl来做它setpgrp(请注意与POSIX略有不同的名称).传递零作为PID来修改perl进程本身的组:

setpgrp(0, 12345) || die "$!"
Run Code Online (Sandbox Code Playgroud)

您可能认为可以使用来自bash的perl来设置bash进程的组($$例如,通过传递给perl脚本),但我不认为perl进程能够修改进程的组.它没有叉.

根据您要执行的操作,各种shell中的作业控制功能可能以不同的方式为您提供所需的功能,例如,如果您只想从终端分离.

更新:我觉得奇怪的是,这个答案收到了几个没有明确解释原因的投票.我的猜测是,downvoters误解了这个问题,即询问如何更改当前 shell 的进程组.或者他们可能知道如何从shell中执行setpgrp但是保守秘密.

  • 刚检查,交互式shell在新进程组中运行程序.非交互式(例如由`cron`启动的)在同一进程组中运行程序(因为没有控制终端,所以不需要在进程之间复用它).并且使用shell内置函数无法更改进程组. (3认同)
  • 我猜他们不喜欢perl依赖,但这对你的答案不公平.IMO和downvote应该要求发表评论 (3认同)

PSk*_*cik 5

如果打开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
Run Code Online (Sandbox Code Playgroud)

以后运行的新进程程序组set -m将接管终端的前台进程组,除非它们在后台运行。

set -m显然是半标准,通过POSIX如果实现支持“用户可携性工具”要求。在实践中它的工作原理上bashdashkshpdkshshyash,和zshposh没有它。


Ing*_*kat 5

如果您的目的是清理任何生成的子 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.
Run Code Online (Sandbox Code Playgroud)

嵌套命令

当执行子 shell 清理的一个脚本被另一个此类脚本调用时,会带来另一种复杂情况。流程组领导不嵌套;一旦脚本占据主导地位,它的生命周期就不再受父脚本控制,因此当父脚本被中断或终止时,嵌套脚本将继续存在。这不是用户通常想要的。

以下脚本片段使用协作模型扩展了上述实现,以便只有顶层脚本承担进程组领导权,并通过导出 向子 shell 指示这一点$PGID。如果子 shell 找到现有的领导者,它本身不会承担领导权,并将其自己的清理任务限制为剩余作业。只有当顶层脚本退出时,其他子 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
Run Code Online (Sandbox Code Playgroud)