Dapatkan daftar proses keturunan secara elegan

23

Saya ingin mendapatkan daftar semua proses yang turun (misalnya anak-anak, cucu-cucu, dll) dari $pid. Ini adalah cara paling sederhana yang saya buat:

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

Apakah ada perintah, atau cara sederhana untuk mendapatkan daftar lengkap dari semua proses turunan?

STenyaK
sumber
Apakah ada alasan Anda membutuhkan semuanya dalam satu jalur? Apa yang kamu lakukan dengan output itu? Saya merasa ini adalah masalah besar, dan Anda mengajukan pertanyaan yang salah.
jordanm
Saya tidak peduli tentang format selama itu bersih (yaitu saya tidak peduli tentang '\n'dibatasi vs ' 'dibatasi). Kasus penggunaan praktis adalah: a) skrip daemonizer yang saya tulis dari masokisme murni (khususnya, fungsi "stop" harus berurusan dengan pohon proses apa pun yang proses daemonisasi telah hasilkan); dan b) skrip batas waktu yang akan membunuh apa pun yang berhasil dibuat oleh proses batas waktu.
STenyaK
2
@STenyaK Kasus penggunaan Anda membuat saya berpikir Anda sedang mencari grup proses dan argumen negatif untuk kill. Lihat unix.stackexchange.com/questions/9480/… , unix.stackexchange.com/questions/50555/…
Gilles 'SO- stop being evil'
@Gilles menggunakan ps ax -opid,ppid,pgrp,cmdSaya melihat ada banyak proses yang berbagi sama pgrpdengan subtree persis yang ingin saya bunuh. (Selain itu, saya tidak dapat melihat setpgrpprogram terdaftar di mana saja dalam paket debian stable: packages.debian.org/... )
STenyaK
1
Kasus penggunaan lain: renice / ionice pada seluruh pohon proses yang memakan terlalu banyak sumber daya, misalnya bangunan paralel besar.
Cheetah

Jawaban:

15

Berikut ini agak sederhana, dan memiliki keuntungan tambahan mengabaikan angka dalam nama perintah:

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

Atau dengan Perl:

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

Kami sedang mencari angka di dalam tanda kurung sehingga kami tidak, misalnya, memberi 2 sebagai proses anak-anak ketika kami bertemu gif2png(3012). Tetapi jika nama perintah berisi nomor yang diurung, semua taruhan dimatikan. Hanya sejauh ini pemrosesan teks dapat membawa Anda.

Jadi saya juga berpikir bahwa kelompok proses adalah cara untuk pergi. Jika Anda ingin menjalankan proses dalam grup prosesnya sendiri, Anda dapat menggunakan alat 'pgrphack' dari paket Debian 'daemontools':

pgrphack my_command args

Atau Anda bisa kembali ke Perl:

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

Satu-satunya peringatan di sini adalah bahwa grup proses tidak bersarang, jadi jika beberapa proses membuat grup proses sendiri, subproses tidak akan lagi berada di grup yang Anda buat.

Jander
sumber
Proses anak sewenang-wenang dan mungkin atau mungkin tidak menggunakan grup proses itu sendiri (saya tidak dapat mengasumsikan apa pun). Namun jawaban Anda datang paling dekat dengan apa yang dilihat dapat dicapai di Linux, jadi saya akan menerimanya. Terima kasih.
STenyaK
Ini sangat berguna!
Michal Gallovic
Pipa pstree juga akan menyertakan id utas, yaitu ID utas yang telah $ pid mulai.
maxschlepzig
Anda dapat menggunakan single 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
}
Russell Davis
sumber
Ini akan menjadi hanya perlu dicatat bahwa ini akan bekerja pada kerang modern ( bash, zsh, fish, dan bahkan ksh 99), tapi mungkin tidak bekerja pada kerang yang lebih tua, misalnyaksh 88
grochmal
@ grochmal, lihat jawaban saya di bawah ini untuk solusi traversal yang berfungsi di ksh-88.
maxschlepzig
1

Versi terpendek yang saya temukan yang juga berurusan dengan perintah seperti pop3d:

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

Ini berkaitan salah jika Anda memiliki perintah yang memiliki nama aneh seperti: my(23)prog.

Ole Tange
sumber
1
Ini tidak berfungsi untuk perintah yang menjalankan beberapa utas (karena pstree juga mencetak ID tersebut).
maxschlepzig
@maxschlepzig Melihat bahwa sangat masalah dengan ffmpegmenggunakan utas. Padahal, dari pengamatan cepat, tampaknya bahwa utas diberikan dengan nama mereka di dalam kurung kurawal { },.
Gypsy Spellweaver
1

Ada juga masalah kebenaran. Mengurai output pstreesecara naif bermasalah karena beberapa alasan:

  • pstree menampilkan PID dan id dari utas (nama ditampilkan dalam kurung kurawal)
  • nama perintah mungkin mengandung kurung kurawal, angka dalam tanda kurung yang membuat penguraian yang andal menjadi mustahil

Jika Anda memiliki Python dan psutilpaket yang diinstal, Anda dapat menggunakan cuplikan ini untuk mendaftar semua proses turunan:

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

(Paket psutil misalnya diinstal sebagai dependensi dari tracerperintah yang tersedia di Fedora / CentOS.)

Atau, Anda bisa melakukan traversal pertama-lebar dari pohon proses di shell bourne:

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

Untuk menghitung transitif-penutupan pid, bagian ekor dapat dihilangkan.

Perhatikan bahwa di atas tidak menggunakan rekursi dan juga berjalan di ksh-88.

Di Linux, seseorang dapat menghilangkan pgreppanggilan dan bukannya membaca informasi dari /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

Ini lebih efisien karena kami menyimpan satu fork / exec untuk setiap PID dan pgrepmelakukan beberapa pekerjaan tambahan di setiap panggilan.

maxschlepzig
sumber
1

Versi Linux ini hanya membutuhkan / proc dan ps. Itu diadaptasi dari bagian terakhir dari jawaban sempurna @ maxschlepzig . Versi ini membaca / proc langsung dari shell alih-alih melahirkan sub-proses dalam satu lingkaran. Ini sedikit lebih cepat dan bisa dibilang sedikit lebih elegan, seperti permintaan judul utas ini.

#!/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
stepse
sumber
0

Dalam masing-masing dari dua kasus penggunaan Anda (yang tampaknya sangat artifisial), mengapa Anda ingin membunuh sub-proses beberapa proses yang tidak menguntungkan? Bagaimana Anda tahu lebih baik daripada sebuah proses ketika anak-anaknya harus hidup atau mati? Ini sepertinya desain yang buruk bagi saya; suatu proses harus membersihkan setelah itu sendiri.

Jika Anda benar-benar tahu lebih baik, maka Anda harus mencari-cari sub-proses ini, dan 'proses yang terampas' tampaknya terlalu bodoh untuk dipercaya fork(2).

Anda harus menghindari menyimpan daftar proses anak atau merendahkan melalui pohon proses, misalnya dengan menempatkan proses anak dalam kelompok proses terpisah seperti yang disarankan oleh @Gilles.

Dalam kasus apa pun, saya menduga bahwa proses daemonisasi Anda akan lebih baik menciptakan kumpulan thread pekerja (yang tentu mati bersama dengan proses yang mengandung) daripada pohon sub-sub-sub-proses yang mendalam, yang harus dibersihkan sesuatu di suatu tempat kemudian .

AnotherSmellyGeek
sumber
2
Kedua use case digunakan dalam lingkungan integrasi / pengujian yang berkelanjutan, sehingga mereka harus berurusan dengan kemungkinan bug yang ada dalam proses anak. Bug ini mungkin memanifestasikan dirinya sebagai ketidakmampuan untuk mematikan diri mereka sendiri atau anak-anak mereka, jadi saya perlu cara untuk memastikan bahwa saya dapat menutup semuanya dalam kasus terburuk.
STenyaK
1
Kalau begitu, saya dengan @Gilles dan @Jander; kelompok proses adalah cara terbaik.
AnotherSmellyGeek
0

Berikut ini skrip pembungkus pgrep yang memungkinkan Anda menggunakan pgrep dan mendapatkan semua keturunan secara bersamaan.

~/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

Aktifkan dengan cara yang sama Anda akan memanggil pgrep normal, seperti pgrep_recursive -U $USER javauntuk menemukan semua proses dan sub-proses Java dari pengguna saat ini.

nolimpl
sumber
1
Karena ini bash, saya merasa kode yang digunakan untuk bergabung dengan PID dengan pembatas dapat diganti dengan pengaturan IFSdan menggunakan array ( "${array[*]}").
muru