Diberikan dua perintah latar belakang, hentikan yang tersisa saat salah satu keluar

14

Saya memiliki skrip bash sederhana yang menjalankan dua server:

#!/bin/bash
(cd ./frontend && gulp serve) & (cd ./backend && gulp serve --verbose)

Jika perintah kedua keluar, sepertinya perintah pertama terus berjalan.

Bagaimana saya bisa mengubah ini sehingga jika salah satu perintah keluar, yang lain dihentikan?

Perhatikan bahwa kita tidak perlu memeriksa tingkat kesalahan dari proses latar belakang, hanya apakah sudah keluar.

blah238
sumber
Mengapa tidak gulp ./fronend/serve && gulp ./backend/serve --verbose?
heemayl
serveadalah argumen, bukan file, jadi direktori saat ini perlu diatur.
blah238
1
Juga ini adalah proses jangka panjang yang perlu dijalankan secara bersamaan, maaf jika itu tidak jelas.
blah238

Jawaban:

21

Ini memulai kedua proses, menunggu yang pertama selesai dan kemudian membunuh yang lain:

#!/bin/bash
{ cd ./frontend && gulp serve; } &
{ cd ./backend && gulp serve --verbose; } &
wait -n
pkill -P $$

Bagaimana itu bekerja

  1. Mulailah:

    { cd ./frontend && gulp serve; } &
    { cd ./backend && gulp serve --verbose; } &

    Dua perintah di atas memulai kedua proses di latar belakang.

  2. Tunggu

    wait -n

    Ini menunggu pekerjaan latar belakang untuk berakhir.

    Karena -nopsi, ini membutuhkan bash 4.3 atau lebih baik.

  3. Membunuh

    pkill -P $$

    Ini membunuh pekerjaan apa pun yang prosesnya saat ini adalah induknya. Dengan kata lain, ini membunuh semua proses latar belakang yang masih berjalan.

    Jika sistem Anda tidak memiliki pkill, coba ganti baris ini dengan:

    kill 0

    yang juga membunuh grup proses saat ini .

Contoh mudah diuji

Dengan mengubah skrip, kami dapat mengujinya bahkan tanpa gulpdiinstal:

$ cat script.sh 
#!/bin/bash
{ sleep $1; echo one;  } &
{ sleep $2; echo two;  } &
wait -n
pkill -P $$
echo done

Script di atas dapat dijalankan sebagai bash script.sh 1 3dan proses pertama berakhir terlebih dahulu. Atau, seseorang dapat menjalankannya bash script.sh 3 1dan proses kedua akan berakhir terlebih dahulu. Dalam kedua kasus, orang dapat melihat bahwa ini beroperasi seperti yang diinginkan.

John1024
sumber
Ini terlihat hebat. Sayangnya perintah-perintah itu tidak berfungsi di lingkungan bash saya (msysgit). Itu buruk saya karena tidak menentukan itu. Saya akan mencobanya di kotak Linux nyata.
blah238
(1) Tidak semua versi bashmendukung -nopsi untuk waitperintah. (2) Saya setuju 100% dengan kalimat pertama - solusi Anda memulai dua proses, menunggu yang pertama selesai dan kemudian membunuh yang lain. Tetapi pertanyaannya mengatakan "... jika salah satu perintah keluar , yang lain dihentikan?" Saya percaya bahwa solusi Anda bukanlah yang diinginkan OP. (3) Mengapa Anda mengubah (…) &ke { …; } &? The &pasukan daftar (perintah kelompok) untuk menjalankan dalam subkulit pula. IMHO, Anda telah menambahkan karakter dan mungkin menimbulkan kebingungan (saya harus melihatnya dua kali untuk memahaminya) tanpa manfaat.
G-Man Mengatakan 'Reinstate Monica'
1
John benar, mereka adalah server web yang normalnya tetap berjalan kecuali ada kesalahan atau mereka ditandai untuk berhenti. Jadi saya tidak berpikir kita perlu memeriksa tingkat kesalahan setiap proses, hanya apakah masih berjalan.
blah238
2
pkilltidak tersedia untuk saya tetapi kill 0tampaknya memiliki efek yang sama. Saya juga memperbarui Git saya untuk lingkungan Windows dan sepertinya wait -nberfungsi sekarang, jadi saya menerima jawaban ini.
blah238
1
Menarik. Meskipun saya melihat bahwa perilaku yang didokumentasikan untuk beberapa versi darikill . dokumentasi pada sistem saya tidak menyebutkannya. Namun, kill 0tetap bekerja. Bagus temukan!
John1024
1

Ini rumit. Inilah yang saya rancang; dimungkinkan untuk menyederhanakan / merampingkannya:

#!/bin/sh

pid1file=$(mktemp)
pid2file=$(mktemp)
stat1file=$(mktemp)
stat2file=$(mktemp)

while true; do sleep 42; done &
main_sleeper=$!

(cd frontend && gulp serve           & echo "$!" > "$pid1file";
    wait "$!" 2> /dev/null; echo "$?" > "$stat1file"; kill "$main_sleeper" 2> /dev/null) &
(cd backend  && gulp serve --verbose & echo "$!" > "$pid2file";
    wait "$!" 2> /dev/null; echo "$?" > "$stat2file"; kill "$main_sleeper" 2> /dev/null) &
sleep 1
wait "$main_sleeper" 2> /dev/null

if stat1=$(<"$stat1file")  &&  [ "$stat1" != "" ]  &&  [ "$stat1" != 0 ]
then
        echo "First process failed ..."
        if pid2=$(<"$pid2file")  &&  [ "$pid2" != "" ]
        then
                echo "... killing second process."
                kill "$pid2" 2> /dev/null
        fi
fi
if [ "$stat1" = "" ]  &&  \
   stat2=$(<"$stat2file")  &&  [ "$stat2" != "" ]  &&  [ "$stat2" != 0 ]
then
        echo "Second process failed ..."
        if pid1=$(<"$pid1file")  &&  [ "$pid1" != "" ]
        then
                echo "... killing first process."
                kill "$pid1" 2> /dev/null
        fi
fi

wait
if stat1=$(<"$stat1file")
then
        echo "Process 1 terminated with status $stat1."
else
        echo "Problem getting status of process 1."
fi
if stat2=$(<"$stat2file")
then
        echo "Process 2 terminated with status $stat2."
else
        echo "Problem getting status of process 2."
fi
  • Pertama, mulailah proses ( while true; do sleep 42; done &) yang tidur / berhenti selamanya. Jika Anda yakin bahwa dua perintah Anda akan berakhir dalam jangka waktu tertentu (misalnya, satu jam), Anda dapat mengubahnya ke satu tidur yang akan melebihi itu (misalnya, sleep 3600). Anda kemudian dapat mengubah logika berikut untuk menggunakan ini sebagai batas waktu; yaitu, bunuh proses jika mereka masih berjalan setelah banyak waktu. (Perhatikan bahwa skrip di atas saat ini tidak melakukan itu.)
  • Mulai dua proses asinkron (latar belakang bersamaan).
    • Anda tidak perlu ./melakukannya cd.
    • command & echo "$!" > somewhere; wait "$!" adalah konstruksi rumit yang memulai proses secara tidak sinkron, menangkap PID-nya, dan kemudian menunggu; menjadikannya semacam proses foreground (sinkron). Tapi ini terjadi dalam (…)daftar yang berada di latar belakang secara keseluruhan, sehingga gulpprosesnya berjalan secara tidak sinkron.
    • Setelah salah satu gulpproses keluar, tulis statusnya ke file sementara dan matikan proses "sleep selamanya".
  • sleep 1 untuk melindungi terhadap kondisi balapan di mana proses latar belakang pertama mati sebelum yang kedua mendapat kesempatan untuk menulis PID-nya ke file.
  • Tunggu proses "selamanya tidur" untuk mengakhiri. Ini terjadi setelah salah satu dari gulpproses keluar, seperti yang dinyatakan di atas.
  • Lihat proses latar belakang mana yang dihentikan. Jika gagal, bunuh yang lain.
  • Jika satu proses gagal dan kami membunuh yang lain, tunggu yang kedua untuk membungkus dan menyimpan statusnya ke file. Jika proses pertama selesai dengan sukses, tunggu yang kedua selesai.
  • Periksa status kedua proses.
G-Man Mengatakan 'Reinstate Monica'
sumber
1

Untuk kelengkapan, inilah yang akhirnya saya gunakan:

#!/bin/bash
(cd frontend && gulp serve) &
(cd backend && gulp serve --verbose) &
wait -n
kill 0

Ini berfungsi untuk saya di Git untuk Windows 2.5.3 64-bit. Versi yang lebih lama mungkin tidak menerima -nopsi aktif wait.

blah238
sumber
1

Di sistem saya (Centos), waittidak -njadi saya melakukan ini:

{ sleep 3; echo one;  } &
FOO=$!
{ sleep 6; echo two;  } &
wait $FOO
pkill -P $$

Ini tidak menunggu "baik", melainkan menunggu yang pertama. Tetapi tetap bisa membantu jika Anda tahu server mana yang akan dihentikan terlebih dahulu.

Ondra Žižka
sumber
Apakah waitmemiliki -nopsi atau tidak tergantung pada shell yang Anda gunakan, bukan distribusi Linux.
Kusalananda