kode kembali proses latar belakang yang dapat diandalkan

12

Mari kita asumsikan bagian dari kode bash berikut:

foo > logfile 2>&1 &
foo_pid=$!

while ps -p$foo_pid
do
    ping -c 1 localhost
done

wait $foo_pid

if [[ $? == 0 ]]
then
    echo "foo success"
fi

Apakah aman untuk menganggap bahwa $?memang berisi kode pengembalian foodan bukan kode pengembalian ping? Jika jawaban untuk pertanyaan itu adalah: "Anda tidak dapat menganggap itu." lalu bagaimana saya bisa memodifikasi potongan kode ini untuk memastikan bahwa $?selalu berisi kode pengembalian foo?

Christopher Schmidt
sumber

Jawaban:

12

Dengan bash, Anda akan memiliki jaminan itu kecuali Anda telah memulai pekerjaan latar belakang lain (dan berhati-hatilah bahwa pekerjaan latar belakang dapat dimulai dengan &tetapi juga dengan coprocdan dengan proses substitusi) antara foo &dan wait.

POSIX mensyaratkan shell mengingat status keluar dari setidaknya 25 pekerjaan setelah mereka pergi , tetapi bashmengingat lebih dari itu.

Sekarang, jika Anda melakukannya:

foo & pid=$!
...
bar &
wait "$pid"

Anda tidak mendapat jaminan yang bartidak akan diberi pid yang sama dengan foo(jika footelah dihentikan pada saat bardimulai), jadi meskipun tidak mungkin, wait "$pid"dapat memberi Anda status keluar dari bar.

Anda dapat mereproduksinya dengan:

bash -c '(exit 12; foo) & pid=$!
         while : bar & [ "$pid" != "$!" ]; do :;done
         wait "$pid"; echo "$?"'

yang akan (pada akhirnya) memberi Anda 0alih-alih 12.

Untuk menghindari masalah, salah satu caranya adalah menuliskannya sebagai:

{
  foo_pid=$!

  while ps -p "$foo_pid"
  do
      ping -c 1 localhost
  done

  bar &
  ...

  read <&3 ret
  if [ "$ret" = 0 ]; then
    echo foo was sucessful.
  fi
} 3< <(foo > logfile 2>&1; echo "$?")
Stéphane Chazelas
sumber
4

Ya, Anda dapat mengandalkan wait "$!"untuk mendapatkan status pekerjaan latar belakang. Saat dijalankan sebagai skrip, bash tidak secara otomatis mengumpulkan pekerjaan latar belakang yang diselesaikan. Jadi jika Anda berlari wait, ia akan mengumpulkan pekerjaan pada saat waitdipanggil.

Anda dapat menguji ini dengan skrip sederhana:

#!/bin/bash
sh -c 'sleep 1; exit 22' &
sleep 5
echo "FG: $?"
wait %1
echo "BG: $?"

Yang akan menghasilkan:

FG: 0
BG: 22
Patrick
sumber
Bagian kunci dari pernyataan itu adalah awal, "ketika dijalankan sebagai skrip". Saat interaktif, waittidak berfungsi. Proses dikumpulkan dan status keluar dibuang tepat sebelum prompt ditampilkan (secara default).
Patrick
Saya baru saja mencobanya di bash 4.2.37, 4.1.2, dan 3.2.48. Semuanya berperilaku sama persis (salinan / tempel kode secara harfiah dalam jawaban saya). The wait %1gagal dengan "tidak ada pekerjaan seperti" sebagai proses latar belakang dikumpulkan segera setelah "tidur 5" selesai.
Patrick
Ah ok, maaf, saya mengerti sekarang. Aku merindukanmu %1di tempat $!.
Stéphane Chazelas
Perhatikan bahwa bash -c '(sleep 1;exit 5) & sleep 2; wait %1; echo $?'(jadi non-interaktif juga) gagal mendapatkan status keluar dari pekerjaan yang mati itu. Kedengarannya seperti bug.
Stéphane Chazelas
0

Saya yakin anggapan Anda benar. Berikut adalah kutipan dari man bashtentang menunggu proses latar belakang.

Jika n menentukan proses atau pekerjaan yang tidak ada, status pengembalian adalah 127. Jika tidak, status pengembalian adalah status keluar dari proses terakhir atau pekerjaan yang ditunggu.

Jadi mungkin Anda harus memeriksa 127

Ada pertanyaan serupa dengan jawaban yang sama sekali berbeda dari yang mungkin membantu.

Script Bash menunggu proses dan mendapatkan kode kembali

edit 1

Terinspirasi oleh komentar dan jawaban @ Stephane, saya telah memperluas skripnya. Saya dapat memulai sekitar 34 proses latar belakang sebelum mulai kehilangan jejak.

tback

$ cat tback 
plist=()
elist=()
slist=([1]=12 [2]=15 [3]=17 [4]=19 [5]=21 [6]=23)
count=30

#start background tasksto monitor
for i in 1 2 3 4
do
  #echo pid $i ${plist[$i]} ${slist[$i]}
  (echo $BASHPID-${slist[$i]} running; exit ${slist[$i]}) & 
  plist[$i]=$!
done

echo starting $count background echos to test history
for i in `eval echo {1..$count}`
do
  echo -n "." &
  elist[$i]=$! 
done
# wait for each background echo to complete
for i in `eval echo {1..$count}`
do
  wait ${elist[$i]}
  echo -n $? 
done
echo ""
# Now wait for each monitored process and check return status with expected
failed=0
for i in 1 2 3 4
do
  wait ${plist[$i]}
  rv=$?
  echo " pid ${plist[$i]} returns $rv should be ${slist[$i]}"
  if [[ $rv != ${slist[$i]} ]] 
  then
    failed=1
  fi
done

wait
echo "Complete $failed"
if [[ $failed = "1" ]]
then
  echo Failed
else
  echo Success
fi
exit $failed
$ 

pada sistem saya menghasilkan

$ bash tback
14553-12 running
14554-15 running
14555-17 running
starting 30 background echos to test history
14556-19 running
..............................000000000000000000000000000000
 pid 14553 returns 12 should be 12
 pid 14554 returns 15 should be 15
 pid 14555 returns 17 should be 17
 pid 14556 returns 19 should be 19
Complete 0
Success
X Tian
sumber
1
Tidak, lihat komentar saya untuk jawaban umlaute , dan cobalah sendiribash -c '(exit 12) & sleep 1; wait "$!"; echo "$?"'
Stéphane Chazelas
Saya belum pernah melihat bashjalur yang longgar (bahkan setelah memulai ribuan pekerjaan), contoh saya menunjukkan bahwa pid digunakan kembali, yang mungkin juga Anda amati dalam kasus Anda.
Stéphane Chazelas