How to set process group of a shell script

2019-01-20 06:57发布

问题:

How to set process group of a shell script ? Also I want all the child process to be in the same process group

I expect something similar to setpgid() in C.

回答1:

As PSkocik points out, it is possible to run a process in its own process group, in most shells, by activating job control (“monitor mode”).

(set -m; exec process_in_its_own_group)

Linux has a setsid utility, which runs the command passed as argument in its own session (using the eponymous system call). This is stronger than running it in its own process group à la setpgrp, but that may be ok for your purpose.

If you want to place the process in an existing group rather than in its own group (i.e. if you want the full power of setpgid), there's no common shell utility. You have to use C/Perl/…



回答2:

I'll answer part of what I understand:

How to force current bash shell script to be it self process group:

I put this in the beginning of my bash script:

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

Why do I would need this ?

When launching a program from an interactive bash session, it gets its own new process group. But this is not the case if your program is called from a bash script (non-interactive). If your program relies on being the process group owner in both condition you'll need this.



回答3:

I don't think Bourne, bash, or zsh will let you do that, but you could do it in perl using the built-in setpgrp (note the slight name difference from POSIX). Pass zero as the PID to modify the group of the perl process itself:

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

You might think you could use perl from, say, bash to set the bash process's group (by passing $$ to a perl script, for example), but I don't think the perl process would be able to modify the group of a process that it didn't fork.

Depending on what you're trying to do, the job control features in various shells may give you what you need, in a different way, like if you just want to detach from the terminal.

UPDATE: I think it's strange that this answer has received a couple of down-votes without clear explanation why. My guess is that the downvoters are misunderstanding the question, which is asking how to change the process group of the current shell. Or perhaps they know how to do a setpgrp from the shell but are keeping the secret to themselves.



回答4:

If you turn set -m on, new processes will be spawned in a new process group, and if they're backgrounded, they won't have SIGINT and SIGQUIT ignored.

if  [ $$ = $(ps -o pgid -hp $$) ]; then
   echo already a process group leader;
else
   set -m
   $0 "$@" #optionally with &
   set +m
fi

The new processes group of programs run after set -m takes over as the foreground process group of the terminal, unless they're run in the background.

The set -m is apparently semi-standard, required by POSIX if the implementation supports "User Portability Utilities". In practice it works on bash, dash, ksh, pdksh, sh, yash, and zsh. posh doesn't have it.



回答5:

As @Rob Davis pointed out in his answer, setting process group is not what you want for shells.

Instead you want to use their process control mechanisms. This answer covers doing this for sh on linux and borne. In short:

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

This will kill any jobs opened in the backrground (e.g. with &).



回答6:

Here's a late synthesis, taken from several other good answers here, if your intention is to cleanup any spawned subshell processes (even if the script itself is not directly launched from an interactive shell, but from another process, and therefore doesn't automatically becomes its own process group leader), relaunching the current script as a new process group leader if necessary.

# 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 "${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.