Cara mendapatkan pid baru saja memulai proses

71

Saya ingin memulai proses (mis. Perintah saya) dan mendapatkan pidnya (untuk memungkinkan untuk membunuhnya nanti).

Saya mencoba ps dan memfilter berdasarkan nama, tetapi saya tidak dapat membedakan proses dengan nama

myCommand
ps ux | awk '/<myCommand>/ {print $2}' 

Karena proses nama tidak unik.

Saya dapat menjalankan proses dengan:

myCommand &

Saya menemukan bahwa saya bisa mendapatkan PID ini dengan:

echo $!

Apakah ada solusi yang lebih sederhana?

Saya akan senang mengeksekusi perintah saya dan mendapatkan PID sebagai hasil dari satu perintah baris.

rafalmag
sumber

Jawaban:

77

Apa yang bisa lebih sederhana dari itu echo $!? Sebagai satu baris:

myCommand & echo $!
Jacek Konieczny
sumber
Terima kasih menggabungkan perintah ini dengan "&" banyak membantu saya.
rafalmag
8
dalam skrip bash, dalam satu lingkaran yang memulai program, $! tidak akurat. Terkadang ia mengembalikan pid dari skrip itu sendiri, terkadang grep atau awk run dari skrip solusi apa pun untuk secara khusus mendapatkan pid dari proses yang baru saja diluncurkan dalam skenario ini? sesuatu seperti pid = myprogramakan luar biasa
2
NB bahwa ini mengharuskan Anda untuk memulai perintah menggunakan &sebagai baris sebelumnya, jika tidak, gema pada dasarnya kembali kosong.
rogerdpack
2
menugaskan ke variabel seperti command & echo $!membekukan eksekusi pada langkah ini :(
Shashank Vivek
26

Bungkus perintah dalam skrip kecil

#!/bin/bash
yourcommand &
echo $! >/path/to/pid.file
user9517 mendukung GoFundMonica
sumber
20

Anda dapat menggunakan sh -cdan execuntuk mendapatkan PID perintah bahkan sebelum dijalankan.

Untuk memulai myCommand, sehingga PID-nya dicetak sebelum mulai dijalankan, Anda dapat menggunakan:

sh -c 'echo $$; exec myCommand'

Bagaimana itu bekerja:

Ini memulai shell baru, mencetak PID dari shell itu, dan kemudian menggunakan execbuiltin untuk mengganti shell dengan perintah Anda, memastikan ia memiliki PID yang sama. Ketika shell Anda menjalankan perintah dengan execbuiltin, shell Anda sebenarnya menjadi perintah itu , daripada perilaku yang lebih umum dari forking salinan baru itu sendiri, yang memiliki PID sendiri yang terpisah dan yang kemudian menjadi perintah.

Saya menemukan ini jauh lebih sederhana daripada alternatif yang melibatkan eksekusi asinkron (dengan &), kontrol pekerjaan, atau pencarian dengan ps. Pendekatan-pendekatan itu baik-baik saja, tetapi kecuali jika Anda memiliki alasan khusus untuk menggunakannya - misalnya, mungkin perintah sudah berjalan, dalam hal mencari PID atau menggunakan kontrol pekerjaan akan masuk akal - saya sarankan mempertimbangkan cara ini terlebih dahulu. (Dan saya pasti tidak akan mempertimbangkan untuk menulis naskah yang rumit atau program lain untuk mencapai ini).

Jawaban ini termasuk contoh dari teknik ini.


Sebagian dari perintah itu kadang-kadang bisa dihilangkan, tetapi tidak biasanya.

Bahkan jika shell yang Anda gunakan adalah gaya Bourne dan karenanya mendukung execbuiltin dengan semantik ini, Anda biasanya tidak boleh mencoba untuk menghindari menggunakan sh -c(atau setara) untuk membuat proses shell baru yang terpisah untuk tujuan ini, karena:

  • Setelah shell menjadi myCommand, tidak ada shell yang menunggu untuk menjalankan perintah berikutnya. sh -c 'echo $$; exec myCommand; footidak akan dapat mencoba menjalankan foosetelah mengganti sendiri dengan myCommand. Kecuali Anda menulis skrip yang menjalankan ini sebagai perintah terakhir, Anda tidak bisa hanya menggunakan echo $$; exec myCommanddi shell di mana Anda menjalankan perintah lain.
  • Anda tidak dapat menggunakan subkulit untuk ini. (echo $$; exec myCommand)mungkin secara sintaksis lebih bagus daripada sh -c 'echo $$; exec myCommand', tetapi ketika Anda menjalankan $$di dalam ( ), itu memberikan PID dari shell induk, bukan dari subkulit itu sendiri. Tapi itu PID subkulit yang akan menjadi PID dari perintah baru. Beberapa shell menyediakan mekanisme non-portabel mereka sendiri untuk menemukan PID subkulit, yang dapat Anda gunakan untuk ini. Secara khusus, dalam Bash 4 , (echo $BASHPID; exec myCommand)berhasil.

Akhirnya, perhatikan bahwa beberapa shell akan melakukan optimasi di mana mereka menjalankan perintah seolah-olah oleh exec(yaitu, mereka melupakan forking terlebih dahulu) ketika diketahui bahwa shell tidak perlu melakukan apa-apa sesudahnya. Beberapa shell mencoba melakukan ini kapan saja itu adalah perintah terakhir untuk dijalankan, sementara yang lain hanya akan melakukannya ketika tidak ada perintah lain sebelum atau setelah perintah, dan yang lain tidak akan melakukannya sama sekali. Efeknya adalah bahwa jika Anda lupa untuk menulis execdan hanya menggunakan sh -c 'echo $$; myCommand'maka itu kadang - kadang akan memberi Anda PID yang tepat pada beberapa sistem dengan beberapa shell. Saya sarankan agar tidak mengandalkan perilaku seperti itu , dan alih-alih selalu termasuk execsaat itulah yang Anda butuhkan.

Eliah Kagan
sumber
Sebelum saya dapat menjalankan myCommand, saya perlu mengatur sejumlah variabel lingkungan di skrip bash saya. Apakah ini akan terbawa ke lingkungan di mana execperintah dijalankan?
user5359531
Sepertinya lingkungan saya tidak terbawa ke execperintah. Namun, pendekatan ini tidak berfungsi ketika myCommandmemulai proses lain, yang mana Anda perlu bekerja dengannya; ketika saya mengeluarkan di kill -INT <pid>mana piddiperoleh dengan cara ini, sinyal tidak mencapai sub-proses yang dimulai oleh myCommand, sedangkan jika saya berjalan myCommand di sesi saat ini dan Ctrl + C, sinyal menyebar dengan benar.
user5359531
1
Saya mencoba ini, tetapi pid dari proses myCommand tampaknya menjadi keluaran pid oleh echo $$ +1. Apakah saya melakukan sesuatu yang salah?
crobar
Perintah saya terlihat seperti ini:sh -c 'echo $$; exec /usr/local/bin/mbdyn -f "input.file" -o "/path/to/outputdir" > "command_output.txt" 2>&1 &'
crobar
7

Saya tidak tahu solusi yang lebih sederhana, tetapi tidak menggunakan $! cukup baik? Anda selalu dapat menetapkan nilai ke beberapa variabel lain jika Anda membutuhkannya nanti, seperti yang dikatakan oleh orang lain.

Sebagai catatan, alih-alih perpipaan dari ps Anda bisa menggunakan pgrepatau pidof.

carlpett
sumber
5

gunakan exec dari skrip bash setelah mendaftarkan pid ke file:

contoh:

misalkan Anda memiliki skrip bernama "forever.sh" yang ingin Anda jalankan dengan args p1, p2, p3

kode sumber forever.sh:

#!/bin/sh

while [ 1 -lt 2 ] ; do
    logger "$0 running with parameters \"$@\""
    sleep 5
done

buat reaper.sh:

#!/bin/sh

echo $$ > /var/run/$1.pid
exec "$@"

jalankan forever.sh melalui reaper.sh:

./reaper.sh ./forever.sh p1 p2 p3 p4 &

forever.sh tidak lebih dari mencatat baris ke syslog setiap 5 detik

Anda sekarang memiliki pid di /var/run/forever.sh.pid

cat /var/run/forever.sh.pid 
5780

dan forever.sh menjalankan aok. syslog grep:

Nov 24 16:07:17 pinkpony cia: ./forever.sh running with parameters "p1 p2 p3 p4"

Anda bisa melihatnya di tabel proses:

ps axuwww|grep 'forever.sh p1' |grep -v grep
root      5780  0.0  0.0   4148   624 pts/7    S    16:07   0:00 /bin/sh ./forever.sh p1 p2 p3 p4
pengguna237419
sumber
3
oh, dan "oneliner": / bin / sh -c 'echo $$> / tmp / my.pid && exec program args' &
user237419
1
Untuk benar melestarikan spasi internal dalam argumen, Anda harus menggunakan exec "$@"bukan exec $*. Secara teknis apa yang perlu Anda pertahankan bukanlah spasi putih tetapi kemunculan karakter dalam parameter shell IFS (yang default untuk spasi, tab, dan baris baru).
Chris Johnsen
titik diambil. :)
user237419
Terima kasih, saya tidak tahu parameter $$. Ini bisa sangat berguna.
rafalmag
3

Di bash shell alternatif untuk $!mungkin adalah jobs -pbuilt-in. Dalam beberapa kasus, !in $!akan diinterpretasikan oleh shell sebelum (atau bukannya) ekspansi variabel, yang mengarah ke hasil yang tidak terduga.

Ini, misalnya, tidak akan berfungsi:

((yourcommand) & echo $! >/var/run/pidfile)

sementara ini akan:

((yourcommand) & jobs -p >/var/run/pidfile)
mustaccio
sumber
Saya pikir Anda maksud ((perintah Anda) & pekerjaan -p> / var / run / pidfile).
Dom
1

Anda dapat menggunakan sesuatu seperti:

$ myCommand ; pid=$!

Atau

$ myCommand && pid=$!

Dua perintah dapat berupa sambungan menggunakan ;atau &&. Dalam kasus kedua, pid akan ditetapkan hanya jika perintah pertama berhasil. Anda bisa mendapatkan id proses dari $pid.

Khaled
sumber
3
OP ingin mendapatkan PID sehingga ia dapat membunuhnya nanti. ; dan && meminta proses asli untuk keluar sebelum $ echo! dieksekusi.
user9517 mendukung GoFundMonica
Ya kamu benar. Ini akan memberi Anda pid setelah perintah saya berakhir.
Khaled
6
Referensi $!setelah &&atau ;tidak akan pernah memberi Anda PID proses dimulai untuk sisi kiri pemisah perintah. $!hanya diatur untuk proses yang diluncurkan secara tidak sinkron (mis. biasanya dengan &tetapi beberapa shell juga memiliki metode lain).
Chris Johnsen
ini membantu, dan mengapa tidak ada yang memilih kecuali saya? kerja bagus :)
temple
1

Ini sedikit jawaban hack-y dan kemungkinan tidak akan bekerja untuk kebanyakan orang. Ini juga merupakan metode risiko keamanan yang sangat besar, jadi jangan lakukan itu kecuali Anda yakin Anda akan aman dan inputnya sudah disanitasi dan ... yah, Anda mengerti.

Kompilasi program C kecil di sini menjadi biner yang disebut start(atau apa pun yang Anda inginkan), kemudian jalankan program Anda sebagai./start your-program-here arg0 arg1 arg2 ...

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    if (argc >= 2)
    {
        printf("%lu\n", (long unsigned) getpid());
        if (execvp(argv[1], &argv[1]) < 0)
        {
            perror(NULL);
            return 127;
        }
    }
    return 0;
}

Singkatnya, ini akan mencetak PID stdout, lalu memuat program Anda ke dalam proses. Seharusnya masih memiliki PID yang sama.

tonysdg
sumber
Bagaimana saya bisa mencatat nomor PID yang dikembalikan oleh kode ini dalam variabel bash? Untuk alasan yang tidak jelas bagi saya, stdoutsepertinya tidak dicatat dalam contoh ini:RESULT="$(./start_and_get_pid.out echo yo)"; echo "$RESULT"
Tfb9
@ Tfb9: Saya sejujurnya merekomendasikan salah satu pendekatan lain; Saya menulis ini 2+ tahun yang lalu dan sejauh ini merupakan salah satu metode peretas / rawan kesalahan yang disajikan (atau saya pikir demikian).
tonysdg
Terima kasih atas catatannya. Saya pikir saya telah memecahkan masalah saya dengan inisleep 10 & PID_IS=$!; echo $PID_IS
Tfb9