【问题标题】:How to set process group of a shell script如何设置shell脚本的进程组
【发布时间】:2011-09-26 20:08:09
【问题描述】:

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

我希望类似于 C 中的 setpgid()。

【问题讨论】:

  • 我想从同一个shell脚本(自己)设置进程组。

标签: linux shell process process-group


【解决方案1】:

作为PSkocik points out,可以通过激活作业控制(“监控模式”)在其自己的进程组中运行一个进程,在大多数 shell 中。

(set -m; exec process_in_its_own_group)

Linux 有一个setsid 实用程序,它运行在它自己的session 中作为参数传递的命令(使用eponymous system call)。这比在它自己的 process group à la setpgrp 中运行它要强大,但这可能适合您的目的。

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

【讨论】:

  • 是否因为技术障碍而缺少setpgrp 实用程序?
  • @PiotrDobrogost 没有。在大多数 shell 中都可以使用set -m 完成(参见PSkocik's answer)。
  • 试图用它来解决Is there any way to exit “less” follow mode without ^C? 但没有运气。将(…) | less -R 更改为(…) | (set -m; exec less -R) 后,我再也没有输出了。
  • set -m 需要一个可能不存在的 tty
【解决方案2】:

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

如何强制当前的 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 脚本(非交互式)调用的,则情况并非如此。如果您的程序在这两种情况下都依赖于进程组所有者,那么您将需要这个。

【讨论】:

  • 非常感谢您拯救了我的灵魂!我花了很多时间来实现我的目标,而您的解决方案是这个馅饼上的一颗樱桃!
  • 我不明白你为什么将变量“pid”传递给你的函数 pgid_from_pid()。您的函数能够读取外部变量,并且为了在函数中使用传递的参数,您需要在函数中使用常见的 $1、$2 语法来访问它们。
  • @Jadzia 缺少一行,抱歉。感谢您发现这一点。已编辑。
  • 为什么需要 grep 调用? AFAICT ps 命令除了 pgid 不会输出任何东西。
【解决方案3】:

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

setpgrp(0, 12345) || die "$!"

您可能认为您可以使用来自 bash 的 perl 来设置 bash 进程的组(例如,通过将 $$ 传递给 perl 脚本),但我认为 perl 进程无法修改它没有派生的进程组。

根据您要执行的操作,各种 shell 中的作业控制功能可能会以不同的方式为您提供所需的内容,就像您只想从终端分离一样。

更新:我认为奇怪的是,这个答案在没有明确解释原因的情况下收到了几票否决票。我的猜测是投反对票的人误解了这个问题,即询问如何更改 当前 shell 的进程组。或者也许他们知道如何从 shell 执行 setpgrp,但对自己保密。

【讨论】:

  • 刚刚检查过,交互式 shell 在新进程组中运行程序。非交互式的(如cron 启动的)在同一个进程组中运行程序(因为没有控制终端,所以进程之间不需要多路复用)。并且无法使用 shell 内置程序更改进程组。
  • 我的回答是关于改变当前进程的进程组。请具体说明我所说的不正确,以便我重新评估。
  • 我猜他们不喜欢 perl 依赖项,但这对您的回答不公平。 IMO 和 downvote 应该需要评论
【解决方案4】:

如果您打开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 需要它。 在实践中,它适用于bashdashkshpdkshshyashzshposh 没有。

【讨论】:

    【解决方案5】:

    如果您的意图是清理任何生成的子shell进程(即使脚本本身不是直接从交互式shell启动,而是从另一个进程启动,因此不t 自动成为它自己的进程组领导),如有必要,重新启动当前脚本作为新的进程组领导。

    # 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 向子shell 表明这一点。如果一个 subshel​​l 找到了一个现有的领导者,它不会自己承担领导权,并将自己的清理任务限制在剩余的工作中。只有在顶级脚本退出后,其他子 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
    

    【讨论】:

    • 谢谢,我认为这是最好的答案。出于某种原因,setsid 技术(是的,前面介绍过)对我有用,而 set -m 却没有。
    【解决方案6】:

    正如@Rob Davis 在his answer 中指出的那样,设置进程组不是你想要的shell。

    相反,您想使用他们的流程控制机制。 This answer 涵盖了在 linuxborne 上为 sh 执行此操作。简而言之:

    #! /bin/sh
    # Kill all opened jobs on exit.
    trap 'kill $(jobs -p)' EXIT
    

    这将杀死在后台打开的所有作业(例如使用&)。

    【讨论】:

    • 这种方法可能是可行的,但你做错了。 $(jobs -p) 将强制 jobs -p 进入一个子shell,其中作业列表将为空(尝试 `sleep 10 & sleep & sleep & 。您需要使用将 pid 列表写入然后从临时文件中读取它。至少对于大多数 shell(ksh 和 bash 设法避免使用子 shell,并且它们也对内置的 trap 做了类似的魔法——您的解决方案应该在这两个 shell 中工作,但不能在其余 shell 中工作)。
    猜你喜欢
    • 2011-03-05
    • 1970-01-01
    • 2020-06-18
    • 2014-07-13
    • 1970-01-01
    • 2015-03-30
    • 2013-05-08
    • 1970-01-01
    • 2017-09-24
    相关资源
    最近更新 更多