子孫プロセスのリストをエレガントに取得する


23

から派生するすべてのプロセス(子、孫など)のリストを取得したいと思い$pidます。これは私が思いついた最も簡単な方法です:

pstree -p $pid | tr "\n" " " |sed "s/[^0-9]/ /g" |sed "s/\s\s*/ /g"

すべての子孫プロセスの完全なリストを取得するためのコマンド、またはより簡単な方法はありますか?


すべてを1行で必要とする理由はありますか?その出力で何をしていますか?これはxyの問題であり、あなたは間違った質問をしていると感じています。
ヨルダン

クリーンである限り、形式は気にしません(つまり、'\n'区切り文字と' '区切り文字の両方を気にしません)。実際の使用例は次のとおりです。a 純粋なマゾヒズムから書き出したデーモン化スクリプト(具体的には、「停止」機能は、デーモン化されたプロセスが生成したプロセスのツリーを処理する必要があります)。およびb)タイムアウトしたプロセスが作成したものをすべて強制終了するタイムアウトスクリプト。
STenyaK

2
@STenyaKあなたのユースケースは、あなたがプロセスグループとに対する否定的な議論を探していると私に思わせますkill。参照unix.stackexchange.com/questions/9480/...unix.stackexchange.com/questions/50555/...
ジル「SO-停止されて悪」

@Gillesを使用するps ax -opid,ppid,pgrp,cmdと、強制終了しpgrpたいサブツリーと同じものを共有する多くのプロセスがあることがわかります。(さらに、setpgrpプログラムはdebian安定版パッケージのどこにもリストされていません:packages.debian.org/…
STenyaK

1
別のユースケース:プロセスツリー全体でrenice / ioniceが大量のリソースを消費している(例:大規模な並列ビルド)。
チーター

回答:


15

以下はやや単純で、コマンド名の数字を無視するという追加の利点があります。

pstree -p $pid | grep -o '([0-9]\+)' | grep -o '[0-9]\+'

またはPerlの場合:

pstree -p $pid | perl -ne 'print "$1\n" while /\((\d+)\)/g'

括弧内の数字を探しているので、たとえば、を横切ったときに2を子プロセスとして与えないようにしgif2png(3012)ます。ただし、コマンド名に括弧で囲まれた数字が含まれる場合、すべてのベットはオフになります。これまでのところ、テキスト処理があなたを連れて行くことができます。

それで、私はプロセスグループが進むべき道だと思います。プロセスを独自のプロセスグループで実行する場合は、Debianパッケージ「daemontools」の「pgrphack」ツールを使用できます。

pgrphack my_command args

または、再びPerlを使用できます。

perl -e 'setpgid or die; exec { $ARGV[0] } @ARGV;' my_command args

ここでの唯一の注意点は、プロセスグループがネストされないことです。そのため、あるプロセスが独自のプロセスグループを作成している場合、そのサブプロセスは作成したグループに含まれなくなります。


子プロセスは任意であり、プロセスグループ自体を使用する場合と使用しない場合があります(何も想定できません)。しかし、あなたの答えはLinuxで達成できると思われるものに最も近いので、私はそれを受け入れます。ありがとう。
STenyaK

これはとても役に立ちました!
ミハルガロビッチ

pstreeパイプには、スレッドID、つまり$ pidが開始したスレッドのIDも含まれます。
-maxschlepzig

単一のgrepを使用できますpstree -lp | grep -Po "(?<=\()\d+(?=\))"
。– puchu

7
descendent_pids() {
    pids=$(pgrep -P $1)
    echo $pids
    for pid in $pids; do
        descendent_pids $pid
    done
}

これは、現代のシェル上で動作することは注目にだけ価値がある(だろうbashzshfish、とさえksh 99)が、古い殻、例えば上で動作しない可能性がありますksh 88
grochmal

@ grochmal、ksh-88で機能するトラバーサルソリューションについては、以下の回答を参照してください。
maxschlepzig

1

私が見つけた最も短いバージョンは、次のようなコマンドも正しく処理しますpop3d

pstree -p $pid | perl -ne 's/\((\d+)\)/print " $1"/ge'

次のような変な名前のコマンドがある場合、それは間違って処理されますmy(23)prog


1
これは、いくつかのスレッドを実行しているコマンドでは機能しません(pstreeはこれらのIDも出力するため)。
maxschlepzig

@maxschlepzig ffmpegスレッドの使用に非常に問題があることに気付きました。しかし、簡単な観察から、スレッドには中括弧で囲まれた名前が付けられているようです{ }
ジプシースペルウィーバー

1

正確さの問題もあります。の出力を単純に解析することにpstreeは、いくつかの理由で問題があります。

  • pstreeはPID スレッドのIDを表示します(名前は中括弧で表示されます)
  • コマンド名に中括弧、括弧内の数字が含まれている可能性があり、信頼できる解析が不可能になる

Pythonとpsutilパッケージがインストールされている場合、このスニペットを使用してすべての子孫プロセスをリストできます。

pid=2235; python3 -c "import psutil
for c in psutil.Process($pid).children(True):
  print(c.pid)"

(psutilパッケージは、例えばtracerFedora / CentOSで利用可能なコマンドの依存関係としてインストールされます。)

または、bourneシェルでプロセスツリーの幅優先走査を実行できます。

ps=2235; while [ "$ps" ]; do echo $ps; ps=$(echo $ps | xargs -n1 pgrep -P); \
  done | tail -n +2 | tr " " "\n"

PIDの推移閉包を計算するために、テール部分を省略できます。

上記は再帰を使用せず、ksh-88でも実行されることに注意してください。

Linuxでは、pgrep呼び出しを削除し、代わりに以下から情報を読み取ることができます/proc

ps=2235; while [ "$ps" ]; do echo $ps ; \
  ps=$(for p in $ps; do cat /proc/$p/task/$p/children; done); done \
  | tr " " "\n"' | tail -n +2

PIDごとに1つのfork / execを保存しpgrep、各呼び出しで追加の作業を行うため、これはより効率的です。


1

このLinuxバージョンでは、/ procおよびpsのみが必要です。@maxschlepzigの優れた回答の最後の部分から改作されています。このバージョンは、ループ内でサブプロセスを生成する代わりに、シェルから/ procを直接読み取ります。このスレッドのタイトルが要求するように、それは少し速く、おそらく間違いなくわずかにエレガントです。

#!/bin/dash

# Print all descendant pids of process pid $1
# adapted from /unix//a/339071

ps=${1:-1}
while [ "$ps" ]; do
  echo $ps
  unset ps1 ps2
  for p in $ps; do
    read ps2 < /proc/$p/task/$p/children 2>/dev/null
    ps1="$ps1 $ps2"
  done
  ps=$ps1
done | tr " " "\n" | tail -n +2

0

2つの(一見非常に人工的な)ユースケースのそれぞれで、なぜ不幸なプロセスのサブプロセスを殺したいのですか?その子が生きるか死ぬべきかというプロセスよりも、どうやって知っているのですか?これは私にはデザインが悪いようです。プロセスは自動的にクリーンアップする必要があります。

本当によく知っている場合は、これらのサブプロセスをフォークする必要があります。「デーモン化されたプロセス」は、明らかに信頼できないほど愚かfork(2)です。

子プロセスのリストを保持したり、@ Gillesによって提案されているように別のプロセスグループに子プロセスを配置するなどして、プロセスツリーを探索することは避けてください。

いずれにせよ、デーモン化されたプロセスは、何かがクリーンアップする必要があるサブサブサブプロセスの深いツリーよりもワーカースレッドプールを作成する方が良いと思う。


2
両方のユースケースは継続的な統合/テスト環境で使用されるため、子プロセス/バグに存在するバグの可能性に対処する必要があります。このバグは、自分自身またはその子供を適切にシャットダウンできないことを示している可能性があるため、最悪の場合にすべてを閉じることができるようにする方法が必要です。
STenyaK

1
その場合、私は@Gillesと@Janderと一緒にいます。プロセスグループが最善の方法です。
AnotherSmellyGeek

0

pgrepを使用して、同時にすべての子孫を取得できるpgrepラッパースクリプトを次に示します。

~/bin/pgrep_wrapper

#!/bin/bash

# the delimiter argument must be the first arg, otherwise it is ignored
delim=$'\n'
if [ "$1" == "-d" ]; then
    delim=$2
    shift 2
fi

pids=
newpids=$(pgrep "$@")
status=$?
if [ $status -ne 0 ]; then
    exit $status
fi

while [ "$pids" != "$newpids" ]; do
    pids=$newpids
    newpids=$( { echo "$pids"; pgrep -P "$(echo -n "$pids" | tr -cs '[:digit:]' ',')"; } | sort -u )
done
if [ "$delim" != $'\n' ]; then
    first=1
    for pid in $pids; do
        if [ $first -ne 1 ]; then
            echo -n "$delim"
        else
            first=0
        fi  
        echo -n "$pid"
    done
else
    echo "$pids"
fi

pgrep_recursive -U $USER java現在のユーザーからすべてのJavaプロセスとサブプロセスを検索するなど、通常のpgrepを呼び出すのと同じ方法で呼び出します。


1
これはbashであるため、PIDと区切り文字を結合するために使用されるコードは、IFS配列("${array[*]}")の設定と使用に置き換えることができると感じています。
muru17年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.