Bagaimana cara menunggu di bash untuk beberapa subproses untuk menyelesaikan dan mengembalikan kode keluar! = 0 ketika setiap subproses berakhir dengan kode! = 0?


563

Bagaimana cara menunggu dalam skrip bash untuk beberapa subproses yang dihasilkan dari skrip tersebut untuk menyelesaikan dan mengembalikan kode keluar! = 0 ketika salah satu subproses berakhir dengan kode! = 0?

Skrip sederhana:

#!/bin/bash
for i in `seq 0 9`; do
  doCalculations $i &
done
wait

Script di atas akan menunggu semua 10 subproses yang muncul, tetapi selalu memberikan status keluar 0 (lihat help wait). Bagaimana saya bisa memodifikasi skrip ini sehingga akan menemukan status keluar dari subproses yang muncul dan mengembalikan kode keluar 1 ketika salah satu dari subproses berakhir dengan kode! = 0?

Apakah ada solusi yang lebih baik untuk itu daripada mengumpulkan PID dari subproses, menunggu mereka dalam urutan dan jumlah status keluar?


1
Ini dapat ditingkatkan secara signifikan untuk disentuh wait -n, tersedia dalam bash modern untuk kembali hanya ketika perintah pertama / berikutnya selesai.
Charles Duffy

jika Anda ingin menguji menggunakan Bash, coba ini: github.com/sstephenson/bats
Alexander Mills

2
Pengembangan aktif BATS telah pindah ke github.com/bats-core/bats-core
Potherca

3
@CharlesDuffy wait -nmemiliki satu masalah kecil: jika tidak ada pekerjaan anak yang tersisa (alias kondisi ras), ia mengembalikan status keluar yang tidak nol (gagal) yang dapat dibedakan dari proses anak yang gagal.
drevicko

5
@CharlesDuffy - Anda memiliki wawasan yang luar biasa, dan Anda melakukan layanan besar untuk SO dengan membagikannya. Tampaknya sekitar 80% dari posting SO yang saya baca membuat Anda berbagi berlian pengetahuan yang indah dalam komentar yang harus datang dari lautan pengalaman yang luas. Terimakasih banyak!
Brett Holman

Jawaban:


521

waitjuga (opsional) mengambil PID dari proses untuk menunggu, dan dengan $! Anda mendapatkan PID dari perintah terakhir yang diluncurkan di latar belakang. Ubah loop untuk menyimpan PID dari masing-masing sub-proses menelurkan ke dalam array, dan kemudian loop lagi menunggu pada setiap PID.

# run processes and store pids in array
for i in $n_procs; do
    ./procs[${i}] &
    pids[${i}]=$!
done

# wait for all pids
for pid in ${pids[*]}; do
    wait $pid
done

9
Weel, karena Anda akan menunggu semua proses itu tidak masalah jika misalnya Anda menunggu yang pertama sementara yang kedua sudah selesai (toh yang ke-2 akan dipilih pada iterasi berikutnya). Ini pendekatan yang sama yang akan Anda gunakan dalam C dengan menunggu (2).
Luca Tettamanti

7
Ah, saya melihat - interpretasi yang berbeda :) Saya membaca pertanyaan sebagai berarti "kembali keluar kode 1 langsung ketika salah satu dari subproses keluar".
Alnitak

56
PID mungkin memang digunakan kembali, tetapi Anda tidak bisa menunggu proses yang bukan anak dari proses saat ini (tunggu gagal dalam kasus itu).
tkokoszka

12
Anda juga dapat menggunakan% n untuk merujuk ke pekerjaan berlatar belakang n: th, dan %% untuk merujuk ke yang terbaru.
conny

30
@Nils_M: Anda benar, saya minta maaf. Jadi itu akan menjadi seperti: for i in $n_procs; do ./procs[${i}] & ; pids[${i}]=$!; done; for pid in ${pids[*]}; do wait $pid; done;kan?
synack

285

http://jeremy.zawodny.com/blog/archives/010717.html :

#!/bin/bash

FAIL=0

echo "starting"

./sleeper 2 0 &
./sleeper 2 1 &
./sleeper 3 0 &
./sleeper 2 0 &

for job in `jobs -p`
do
echo $job
    wait $job || let "FAIL+=1"
done

echo $FAIL

if [ "$FAIL" == "0" ];
then
echo "YAY!"
else
echo "FAIL! ($FAIL)"
fi

104
jobs -pmemberikan PID dari subproses yang berada dalam status eksekusi. Ini akan melewati proses jika proses selesai sebelum jobs -pdipanggil. Jadi, jika salah satu dari subproses berakhir sebelumnya jobs -p, status keluar proses itu akan hilang.
tkokoszka

15
Wow, jawaban ini jauh lebih baik daripada yang berperingkat teratas. : /
e40

4
@ e40 dan jawabannya di bawah mungkin bahkan lebih baik. Dan bahkan lebih baik mungkin menjalankan setiap perintah dengan '(cmd; echo "$?" >> "$ tmpfile"), gunakan tunggu ini, dan kemudian baca file untuk gagal. Juga membubuhi keterangan-output. ... atau gunakan saja skrip ini saat Anda tidak terlalu peduli.
HoverHell

Saya ingin menambahkan bahwa jawaban ini lebih baik daripada yang diterima
shurikk

2
@tkokoszka agar akurat jobs -ptidak memberikan PID dari subproses, melainkan GPID . Logika menunggu tampaknya berfungsi, itu selalu menunggu di grup jika grup tersebut ada dan pid jika tidak, tapi ada baiknya untuk menyadari .. terutama jika seseorang harus membangun ini dan memasukkan sesuatu seperti mengirim pesan ke subproses di mana huruf sintaks berbeda tergantung pada apakah Anda memiliki PID atau GPID .. yaitu kill -- -$GPIDvskill $PID
Timo

59

Berikut adalah contoh sederhana menggunakan wait.

Jalankan beberapa proses:

$ sleep 10 &
$ sleep 10 &
$ sleep 20 &
$ sleep 20 &

Kemudian tunggu mereka dengan waitperintah:

$ wait < <(jobs -p)

Atau adil wait(tanpa argumen) untuk semua.

Ini akan menunggu semua pekerjaan di latar belakang selesai.

Jika -nopsi diberikan, menunggu pekerjaan berikutnya berakhir dan mengembalikan status keluarnya.

Lihat: help waitdan help jobsuntuk sintaks.

Namun downside adalah bahwa ini hanya akan mengembalikan status ID terakhir, jadi Anda perlu memeriksa status untuk setiap subproses dan menyimpannya dalam variabel.

Atau buat fungsi perhitungan Anda untuk membuat beberapa file pada kegagalan (kosong atau dengan log gagal), kemudian periksa file itu jika ada, misalnya

$ sleep 20 && true || tee fail &
$ sleep 20 && false || tee fail &
$ wait < <(jobs -p)
$ test -f fail && echo Calculation failed.

2
Bagi mereka yang baru mengenal bash, dua perhitungan dalam contoh di sini adalah sleep 20 && truedan sleep 20 && false- yaitu: ganti dengan fungsi Anda. Untuk memahami &&dan ||, jalankan man bashdan ketik '/' (cari) lalu '^ * Daftar' (sebuah regex) kemudian masukkan: man akan gulir ke bawah ke deskripsi &&dan||
drevicko

1
Anda mungkin harus memeriksa bahwa file 'gagal' tidak ada di awal (atau menghapusnya). Bergantung pada aplikasinya, mungkin juga merupakan ide bagus untuk menambahkan '2> & 1' sebelum ||menangkap STDERR yang gagal juga.
drevicko

saya suka yang ini, ada kekurangannya? sebenarnya, hanya ketika saya ingin mendaftar semua subproses dan mengambil beberapa tindakan, misalnya. mengirim sinyal, bahwa saya akan mencoba untuk melakukan pembukuan pids atau beralih dari pekerjaan. Tunggu selesai, cukupwait
xgwang

Ini akan kehilangan status keluar dari pekerjaan yang gagal sebelum pekerjaan -p disebut
Erik Aronesty

50

Jika Anda telah menginstal GNU Paralel, Anda dapat melakukan:

# If doCalculations is a function
export -f doCalculations
seq 0 9 | parallel doCalculations {}

GNU Parallel akan memberi Anda kode keluar:

  • 0 - Semua pekerjaan berjalan tanpa kesalahan.

  • 1-253 - Beberapa pekerjaan gagal. Status keluar memberikan jumlah pekerjaan yang gagal

  • 254 - Lebih dari 253 pekerjaan gagal.

  • 255 - Kesalahan lainnya.

Tonton video intro untuk mempelajari lebih lanjut: http://pi.dk/1


1
Terima kasih! Tetapi Anda lupa menyebutkan masalah "kebingungan" yang kemudian saya masukkan
nobar

1
Ini terlihat seperti alat yang hebat, tapi saya tidak berpikir di atas berfungsi sebagaimana adanya dalam skrip Bash di mana doCalculationsfungsi didefinisikan dalam skrip yang sama (meskipun OP tidak jelas tentang persyaratan ini). Ketika saya mencoba, parallelkatanya /bin/bash: doCalculations: command not found(dikatakan ini 10 kali untuk seq 0 9contoh di atas). Lihat di sini untuk solusinya.
nobar

3
Yang juga menarik: xargsmemiliki beberapa kemampuan untuk meluncurkan pekerjaan secara paralel melalui -Popsi. Dari sini : export -f doCalculations ; seq 0 9 |xargs -P 0 -n 1 -I{} bash -c "doCalculations {}". Keterbatasan xargsdisebutkan dalam halaman manual untuk parallel.
nobar

Dan jika doCalculationsbergantung pada variabel lingkungan skrip-internal lainnya (khusus PATH, dll.), Mereka mungkin perlu exportdiedit secara eksplisit sebelum diluncurkan parallel.
nobar

4
@ nobar Kebingungan ini disebabkan oleh beberapa paket mengacaukan hal-hal untuk pengguna mereka. Jika Anda menginstal menggunakan wget -O - pi.dk/3 | shAnda tidak akan mendapatkan kebingungan. Jika paket Anda telah mengacaukan segalanya untuk Anda, saya mendorong Anda untuk mengangkat masalah dengan paket Anda. Variabel dan fungsi harus diekspor (ekspor -f) untuk GNU Paralel untuk melihatnya (lihat man parallel: gnu.org/software/parallel/… )
Ole Tange

46

Bagaimana dengan sederhana:

#!/bin/bash

pids=""

for i in `seq 0 9`; do
   doCalculations $i &
   pids="$pids $!"
done

wait $pids

...code continued here ...

Memperbarui:

Seperti yang ditunjukkan oleh banyak komentator, hal di atas menunggu semua proses diselesaikan sebelum melanjutkan, tetapi tidak keluar dan gagal jika salah satu dari mereka gagal, itu dapat dilakukan dengan modifikasi berikut yang disarankan oleh @Bryan, @SamBrightman, dan lainnya :

#!/bin/bash

pids=""
RESULT=0


for i in `seq 0 9`; do
   doCalculations $i &
   pids="$pids $!"
done

for pid in $pids; do
    wait $pid || let "RESULT=1"
done

if [ "$RESULT" == "1" ];
    then
       exit 1
fi

...code continued here ...

1
Menurut menunggu halaman manual, tunggu dengan beberapa PID hanya mengembalikan nilai pengembalian dari proses terakhir yang ditunggu. Jadi Anda memang membutuhkan loop tambahan dan menunggu setiap PID secara terpisah, seperti yang disarankan dalam jawaban yang diterima (dalam komentar).
Vlad Frolov 6-15

1
Karena sepertinya tidak dinyatakan di tempat lain di halaman ini, saya akan menambahkan bahwa loop akan menjadifor pid in $pids; do wait $pid; done
Bryan

1
@bisounours_tronconneuse ya, Anda tahu. Lihat help wait- dengan beberapa ID waitmengembalikan kode keluar yang terakhir saja, seperti yang dikatakan @ vlad-frolov di atas.
Sam Brightman

1
Bryan, @SamBrightman Ok. Saya memodifikasinya dengan rekomendasi Anda.
patapouf_ai

4
Saya memiliki keprihatinan yang jelas dengan solusi ini: bagaimana jika proses yang diberikan keluar sebelum yang sesuai waitdisebut? Ternyata ini bukan masalah: jika Anda waitpada proses yang sudah keluar, waitakan segera keluar dengan status proses yang sudah keluar. (Terima kasih, bashpenulis!)
Daniel Griscom

39

Inilah yang saya buat sejauh ini. Saya ingin melihat bagaimana cara menginterupsi perintah tidur jika seorang anak berhenti, sehingga seseorang tidak perlu menyetel WAITALL_DELAYke penggunaannya.

waitall() { # PID...
  ## Wait for children to exit and indicate whether all exited with 0 status.
  local errors=0
  while :; do
    debug "Processes remaining: $*"
    for pid in "$@"; do
      shift
      if kill -0 "$pid" 2>/dev/null; then
        debug "$pid is still alive."
        set -- "$@" "$pid"
      elif wait "$pid"; then
        debug "$pid exited with zero exit status."
      else
        debug "$pid exited with non-zero exit status."
        ((++errors))
      fi
    done
    (("$#" > 0)) || break
    # TODO: how to interrupt this sleep when a child terminates?
    sleep ${WAITALL_DELAY:-1}
   done
  ((errors == 0))
}

debug() { echo "DEBUG: $*" >&2; }

pids=""
for t in 3 5 4; do 
  sleep "$t" &
  pids="$pids $!"
done
waitall $pids

Orang mungkin bisa melewatkan WAITALL_DELAY itu atau menyetelnya sangat rendah, karena tidak ada proses yang dimulai di dalam loop saya tidak berpikir itu terlalu mahal.
Marian

21

Untuk memparalelkan ini ...

for i in $(whatever_list) ; do
   do_something $i
done

Terjemahkan ke ini ...

for i in $(whatever_list) ; do echo $i ; done | ## execute in parallel...
   (
   export -f do_something ## export functions (if needed)
   export PATH ## export any variables that are required
   xargs -I{} --max-procs 0 bash -c ' ## process in batches...
      {
      echo "processing {}" ## optional
      do_something {}
      }' 
   )
  • Jika kesalahan terjadi dalam satu proses, itu tidak akan mengganggu proses lainnya, tetapi itu akan menghasilkan kode keluar yang tidak nol dari urutan secara keseluruhan .
  • Fungsi dan variabel ekspor mungkin atau mungkin tidak diperlukan, dalam kasus tertentu.
  • Anda dapat mengatur --max-procsberdasarkan pada berapa banyak paralelisme yang Anda inginkan ( 0berarti "sekaligus").
  • GNU Parallel menawarkan beberapa fitur tambahan saat digunakan sebagai pengganti xargs- tetapi tidak selalu diinstal secara default.
  • The forloop tidak benar-benar diperlukan dalam contoh ini karena echo $ipada dasarnya hanya regenerasi output $(whatever_list). Saya hanya berpikir penggunaan forkata kunci membuatnya sedikit lebih mudah untuk melihat apa yang sedang terjadi.
  • Penanganan string Bash dapat membingungkan - Saya telah menemukan bahwa menggunakan tanda kutip tunggal bekerja paling baik untuk membungkus skrip non-sepele.
  • Anda dapat dengan mudah menghentikan seluruh operasi (menggunakan ^ C atau yang serupa), tidak seperti pendekatan yang lebih langsung ke paralelisme Bash .

Berikut ini contoh kerja yang disederhanakan ...

for i in {0..5} ; do echo $i ; done |xargs -I{} --max-procs 2 bash -c '
   {
   echo sleep {}
   sleep 2s
   }'


7

Saya tidak percaya itu mungkin dengan fungsionalitas bawaan Bash.

Anda bisa mendapatkan notifikasi ketika seorang anak keluar:

#!/bin/sh
set -o monitor        # enable script job control
trap 'echo "child died"' CHLD

Namun tidak ada cara yang jelas untuk mendapatkan status keluar anak di penangan sinyal.

Mendapatkan status anak itu biasanya merupakan tugas waitkeluarga fungsi di API POSIX tingkat bawah. Sayangnya dukungan Bash untuk itu terbatas - Anda dapat menunggu satu proses anak tertentu (dan mendapatkan status keluarnya) atau Anda dapat menunggu semuanya , dan selalu mendapatkan hasil 0.

Apa yang tampaknya mustahil untuk dilakukan adalah setara waitpid(-1), yang blok sampai setiap proses anak kembali.


7

Saya melihat banyak contoh bagus yang tercantum di sini, ingin melemparkan milik saya juga.

#! /bin/bash

items="1 2 3 4 5 6"
pids=""

for item in $items; do
    sleep $item &
    pids+="$! "
done

for pid in $pids; do
    wait $pid
    if [ $? -eq 0 ]; then
        echo "SUCCESS - Job $pid exited with a status of $?"
    else
        echo "FAILED - Job $pid exited with a status of $?"
    fi
done

Saya menggunakan sesuatu yang sangat mirip untuk memulai / menghentikan server / layanan secara paralel dan memeriksa setiap status keluar. Bekerja bagus untukku. Semoga ini bisa membantu seseorang!


Ketika saya menghentikannya dengan Ctrl + CI masih melihat proses berjalan di latar belakang.
karsten

2
@karsten - ini masalah yang berbeda. Dengan asumsi Anda menggunakan bash, Anda dapat menjebak kondisi keluar (termasuk Ctrl + C) dan membunuh proses saat ini dan semua anak menggunakantrap "kill 0" EXIT
Phil

@ Phil benar. Karena ini adalah proses latar belakang, membunuh proses induk membiarkan proses anak berjalan. Contoh saya tidak menjebak sinyal apa pun, yang dapat ditambahkan jika perlu seperti yang dinyatakan Phil.
Jason Slobotski

6

Ini adalah sesuatu yang saya gunakan:

#wait for jobs
for job in `jobs -p`; do wait ${job}; done

5

Kode berikut akan menunggu penyelesaian semua perhitungan dan mengembalikan status keluar 1 jika salah satu dari doCalculations gagal.

#!/bin/bash
for i in $(seq 0 9); do
   (doCalculations $i >&2 & wait %1; echo $?) &
done | grep -qv 0 && exit 1

5

Cukup simpan hasil dari shell, misalnya dalam file.

#!/bin/bash
tmp=/tmp/results

: > $tmp  #clean the file

for i in `seq 0 9`; do
  (doCalculations $i; echo $i:$?>>$tmp)&
done      #iterate

wait      #wait until all ready

sort $tmp | grep -v ':0'  #... handle as required

5

Ini versi saya yang berfungsi untuk beberapa pids, mencatat log jika eksekusi terlalu lama, dan menghentikan subproses jika eksekusi membutuhkan waktu lebih lama dari nilai yang diberikan.

function WaitForTaskCompletion {
    local pids="${1}" # pids to wait for, separated by semi-colon
    local soft_max_time="${2}" # If execution takes longer than $soft_max_time seconds, will log a warning, unless $soft_max_time equals 0.
    local hard_max_time="${3}" # If execution takes longer than $hard_max_time seconds, will stop execution, unless $hard_max_time equals 0.
    local caller_name="${4}" # Who called this function
    local exit_on_error="${5:-false}" # Should the function exit program on subprocess errors       

    Logger "${FUNCNAME[0]} called by [$caller_name]."

    local soft_alert=0 # Does a soft alert need to be triggered, if yes, send an alert once 
    local log_ttime=0 # local time instance for comparaison

    local seconds_begin=$SECONDS # Seconds since the beginning of the script
    local exec_time=0 # Seconds since the beginning of this function

    local retval=0 # return value of monitored pid process
    local errorcount=0 # Number of pids that finished with errors

    local pidCount # number of given pids

    IFS=';' read -a pidsArray <<< "$pids"
    pidCount=${#pidsArray[@]}

    while [ ${#pidsArray[@]} -gt 0 ]; do
        newPidsArray=()
        for pid in "${pidsArray[@]}"; do
            if kill -0 $pid > /dev/null 2>&1; then
                newPidsArray+=($pid)
            else
                wait $pid
                result=$?
                if [ $result -ne 0 ]; then
                    errorcount=$((errorcount+1))
                    Logger "${FUNCNAME[0]} called by [$caller_name] finished monitoring [$pid] with exitcode [$result]."
                fi
            fi
        done

        ## Log a standby message every hour
        exec_time=$(($SECONDS - $seconds_begin))
        if [ $((($exec_time + 1) % 3600)) -eq 0 ]; then
            if [ $log_ttime -ne $exec_time ]; then
                log_ttime=$exec_time
                Logger "Current tasks still running with pids [${pidsArray[@]}]."
            fi
        fi

        if [ $exec_time -gt $soft_max_time ]; then
            if [ $soft_alert -eq 0 ] && [ $soft_max_time -ne 0 ]; then
                Logger "Max soft execution time exceeded for task [$caller_name] with pids [${pidsArray[@]}]."
                soft_alert=1
                SendAlert

            fi
            if [ $exec_time -gt $hard_max_time ] && [ $hard_max_time -ne 0 ]; then
                Logger "Max hard execution time exceeded for task [$caller_name] with pids [${pidsArray[@]}]. Stopping task execution."
                kill -SIGTERM $pid
                if [ $? == 0 ]; then
                    Logger "Task stopped successfully"
                else
                    errrorcount=$((errorcount+1))
                fi
            fi
        fi

        pidsArray=("${newPidsArray[@]}")
        sleep 1
    done

    Logger "${FUNCNAME[0]} ended for [$caller_name] using [$pidCount] subprocesses with [$errorcount] errors."
    if [ $exit_on_error == true ] && [ $errorcount -gt 0 ]; then
        Logger "Stopping execution."
        exit 1337
    else
        return $errorcount
    fi
}

# Just a plain stupid logging function to replace with yours
function Logger {
    local value="${1}"

    echo $value
}

Contoh, tunggu hingga ketiga proses selesai, catat peringatan jika eksekusi membutuhkan lebih dari 5 detik, hentikan semua proses jika eksekusi membutuhkan waktu lebih dari 120 detik. Jangan keluar dari program karena kegagalan.

function something {

    sleep 10 &
    pids="$!"
    sleep 12 &
    pids="$pids;$!"
    sleep 9 &
    pids="$pids;$!"

    WaitForTaskCompletion $pids 5 120 ${FUNCNAME[0]} false
}
# Launch the function
someting

4

Jika Anda memiliki bash 4.2 atau yang lebih baru, yang berikut ini mungkin berguna bagi Anda. Ini menggunakan array asosiatif untuk menyimpan nama tugas dan "kode" mereka serta nama tugas dan pids mereka. Saya juga telah membangun metode pembatasan-tingkat sederhana yang mungkin berguna jika tugas Anda menghabiskan banyak waktu CPU atau I / O dan Anda ingin membatasi jumlah tugas bersamaan.

Script meluncurkan semua tugas di loop pertama dan menggunakan hasilnya di yang kedua.

Ini agak berlebihan untuk kasus-kasus sederhana tetapi memungkinkan untuk hal-hal yang cukup rapi. Sebagai contoh, seseorang dapat menyimpan pesan kesalahan untuk setiap tugas dalam array asosiatif lain dan mencetaknya setelah semuanya beres.

#! /bin/bash

main () {
    local -A pids=()
    local -A tasks=([task1]="echo 1"
                    [task2]="echo 2"
                    [task3]="echo 3"
                    [task4]="false"
                    [task5]="echo 5"
                    [task6]="false")
    local max_concurrent_tasks=2

    for key in "${!tasks[@]}"; do
        while [ $(jobs 2>&1 | grep -c Running) -ge "$max_concurrent_tasks" ]; do
            sleep 1 # gnu sleep allows floating point here...
        done
        ${tasks[$key]} &
        pids+=(["$key"]="$!")
    done

    errors=0
    for key in "${!tasks[@]}"; do
        pid=${pids[$key]}
        local cur_ret=0
        if [ -z "$pid" ]; then
            echo "No Job ID known for the $key process" # should never happen
            cur_ret=1
        else
            wait $pid
            cur_ret=$?
        fi
        if [ "$cur_ret" -ne 0 ]; then
            errors=$(($errors + 1))
            echo "$key (${tasks[$key]}) failed."
        fi
    done

    return $errors
}

main

4

Saya baru saja memodifikasi skrip untuk latar belakang dan memparalelkan suatu proses.

Saya melakukan beberapa percobaan (pada Solaris dengan bash dan ksh) dan menemukan bahwa 'tunggu' mengeluarkan status keluar jika bukan nol, atau daftar pekerjaan yang mengembalikan keluar tidak nol ketika tidak ada argumen PID yang disediakan. Misalnya

Pesta:

$ sleep 20 && exit 1 &
$ sleep 10 && exit 2 &
$ wait
[1]-  Exit 2                  sleep 20 && exit 2
[2]+  Exit 1                  sleep 10 && exit 1

Ksh:

$ sleep 20 && exit 1 &
$ sleep 10 && exit 2 &
$ wait
[1]+  Done(2)                  sleep 20 && exit 2
[2]+  Done(1)                  sleep 10 && exit 1

Output ini ditulis ke stderr, jadi solusi sederhana untuk contoh OP dapat berupa:

#!/bin/bash

trap "rm -f /tmp/x.$$" EXIT

for i in `seq 0 9`; do
  doCalculations $i &
done

wait 2> /tmp/x.$$
if [ `wc -l /tmp/x.$$` -gt 0 ] ; then
  exit 1
fi

Sementara ini:

wait 2> >(wc -l)

juga akan mengembalikan hitungan tetapi tanpa file tmp. Ini mungkin juga digunakan dengan cara ini, misalnya:

wait 2> >(if [ `wc -l` -gt 0 ] ; then echo "ERROR"; fi)

Tapi ini tidak jauh lebih berguna daripada file tmp IMO. Saya tidak dapat menemukan cara yang berguna untuk menghindari file tmp sementara juga menghindari menjalankan "tunggu" dalam sebuah subkulit, yang tidak akan berfungsi sama sekali.


3

Saya sudah mencoba ini dan menggabungkan semua bagian terbaik dari contoh lain di sini. Script ini akan menjalankan checkpidsfungsi ketika ada proses latar belakang keluar, dan mengeluarkan status keluar tanpa menggunakan polling.

#!/bin/bash

set -o monitor

sleep 2 &
sleep 4 && exit 1 &
sleep 6 &

pids=`jobs -p`

checkpids() {
    for pid in $pids; do
        if kill -0 $pid 2>/dev/null; then
            echo $pid is still alive.
        elif wait $pid; then
            echo $pid exited with zero exit status.
        else
            echo $pid exited with non-zero exit status.
        fi
    done
    echo
}

trap checkpids CHLD

wait

3
#!/bin/bash
set -m
for i in `seq 0 9`; do
  doCalculations $i &
done
while fg; do true; done
  • set -m memungkinkan Anda menggunakan fg & bg dalam skrip
  • fg, selain menempatkan proses terakhir di latar depan, memiliki status keluar yang sama dengan proses di latar depan
  • while fgakan berhenti berulang ketika ada yang fgkeluar dengan status keluar yang tidak nol

sayangnya ini tidak akan menangani kasus ketika proses di latar belakang keluar dengan status keluar yang tidak nol. (loop tidak akan segera berakhir. Ini akan menunggu proses sebelumnya selesai.)


3

Sudah ada banyak jawaban di sini, tapi saya terkejut tidak ada yang menyarankan menggunakan array ... Jadi inilah yang saya lakukan - ini mungkin berguna untuk beberapa di masa depan.

n=10 # run 10 jobs
c=0
PIDS=()

while true

    my_function_or_command &
    PID=$!
    echo "Launched job as PID=$PID"
    PIDS+=($PID)

    (( c+=1 ))

    # required to prevent any exit due to error
    # caused by additional commands run which you
    # may add when modifying this example
    true

do

    if (( c < n ))
    then
        continue
    else
        break
    fi
done 


# collect launched jobs

for pid in "${PIDS[@]}"
do
    wait $pid || echo "failed job PID=$pid"
done

3

Ini berfungsi, harus sama baiknya jika tidak lebih baik dari jawaban @ HoverHell!

#!/usr/bin/env bash

set -m # allow for job control
EXIT_CODE=0;  # exit code of overall script

function foo() {
     echo "CHLD exit code is $1"
     echo "CHLD pid is $2"
     echo $(jobs -l)

     for job in `jobs -p`; do
         echo "PID => ${job}"
         wait ${job} ||  echo "At least one test failed with exit code => $?" ; EXIT_CODE=1
     done
}

trap 'foo $? $$' CHLD

DIRN=$(dirname "$0");

commands=(
    "{ echo "foo" && exit 4; }"
    "{ echo "bar" && exit 3; }"
    "{ echo "baz" && exit 5; }"
)

clen=`expr "${#commands[@]}" - 1` # get length of commands - 1

for i in `seq 0 "$clen"`; do
    (echo "${commands[$i]}" | bash) &   # run the command via bash in subshell
    echo "$i ith command has been issued as a background job"
done

# wait for all to finish
wait;

echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"

# end

dan tentu saja, saya telah mengabadikan skrip ini, dalam proyek NPM yang memungkinkan Anda untuk menjalankan perintah bash secara paralel, berguna untuk pengujian:

https://github.com/ORESoftware/generic-subshell


trap $? $$tampaknya mengatur kode keluar ke 0 dan PID ke bash shell berjalan saat ini, setiap kali bagi saya
inetknght

Anda benar-benar yakin tentang itu? Tidak yakin apakah itu masuk akal.
Alexander Mills

2

perangkap adalah temanmu. Anda dapat menjebak ERR di banyak sistem. Anda dapat menjebak EXIT, atau pada DEBUG untuk melakukan sepotong kode setelah setiap perintah.

Ini selain semua sinyal standar.


1
Tolong bisakah Anda menjelaskan jawaban Anda dengan beberapa contoh.
ϹοδεMεδιϲ

2
set -e
fail () {
    touch .failure
}
expect () {
    wait
    if [ -f .failure ]; then
        rm -f .failure
        exit 1
    fi
}

sleep 2 || fail &
sleep 2 && false || fail &
sleep 2 || fail
expect

Itu set -e atas membuat skrip Anda berhenti pada kegagalan.

expectakan kembali 1jika ada subjek yang gagal.


2

Tepat untuk tujuan ini saya menulis sebuah bashfungsi yang disebut:for .

Catatan : :fortidak hanya mempertahankan dan mengembalikan kode keluar dari fungsi gagal, tetapi juga mengakhiri semua instance berjalan paralel. Yang mungkin tidak diperlukan dalam kasus ini.

#!/usr/bin/env bash

# Wait for pids to terminate. If one pid exits with
# a non zero exit code, send the TERM signal to all
# processes and retain that exit code
#
# usage:
# :wait 123 32
function :wait(){
    local pids=("$@")
    [ ${#pids} -eq 0 ] && return $?

    trap 'kill -INT "${pids[@]}" &>/dev/null || true; trap - INT' INT
    trap 'kill -TERM "${pids[@]}" &>/dev/null || true; trap - RETURN TERM' RETURN TERM

    for pid in "${pids[@]}"; do
        wait "${pid}" || return $?
    done

    trap - INT RETURN TERM
}

# Run a function in parallel for each argument.
# Stop all instances if one exits with a non zero
# exit code
#
# usage:
# :for func 1 2 3
#
# env:
# FOR_PARALLEL: Max functions running in parallel
function :for(){
    local f="${1}" && shift

    local i=0
    local pids=()
    for arg in "$@"; do
        ( ${f} "${arg}" ) &
        pids+=("$!")
        if [ ! -z ${FOR_PARALLEL+x} ]; then
            (( i=(i+1)%${FOR_PARALLEL} ))
            if (( i==0 )) ;then
                :wait "${pids[@]}" || return $?
                pids=()
            fi
        fi
    done && [ ${#pids} -eq 0 ] || :wait "${pids[@]}" || return $?
}

pemakaian

for.sh:

#!/usr/bin/env bash
set -e

# import :for from gist: https://gist.github.com/Enteee/c8c11d46a95568be4d331ba58a702b62#file-for
# if you don't like curl imports, source the actual file here.
source <(curl -Ls https://gist.githubusercontent.com/Enteee/c8c11d46a95568be4d331ba58a702b62/raw/)

msg="You should see this three times"

:(){
  i="${1}" && shift

  echo "${msg}"

  sleep 1
  if   [ "$i" == "1" ]; then sleep 1
  elif [ "$i" == "2" ]; then false
  elif [ "$i" == "3" ]; then
    sleep 3
    echo "You should never see this"
  fi
} && :for : 1 2 3 || exit $?

echo "You should never see this"
$ ./for.sh; echo $?
You should see this three times
You should see this three times
You should see this three times
1

Referensi


1

Saya menggunakan ini baru-baru ini (terima kasih kepada Alnitak):

#!/bin/bash
# activate child monitoring
set -o monitor

# locking subprocess
(while true; do sleep 0.001; done) &
pid=$!

# count, and kill when all done
c=0
function kill_on_count() {
    # you could kill on whatever criterion you wish for
    # I just counted to simulate bash's wait with no args
    [ $c -eq 9 ] && kill $pid
    c=$((c+1))
    echo -n '.' # async feedback (but you don't know which one)
}
trap "kill_on_count" CHLD

function save_status() {
    local i=$1;
    local rc=$2;
    # do whatever, and here you know which one stopped
    # but remember, you're called from a subshell
    # so vars have their values at fork time
}

# care must be taken not to spawn more than one child per loop
# e.g don't use `seq 0 9` here!
for i in {0..9}; do
    (doCalculations $i; save_status $i $?) &
done

# wait for locking subprocess to be killed
wait $pid
echo

Dari sana orang dapat dengan mudah memperkirakan, dan memiliki pemicu (menyentuh file, mengirim sinyal) dan mengubah kriteria penghitungan (menghitung file yang disentuh, atau apa pun) untuk menanggapi pemicu itu. Atau jika Anda hanya ingin 'any' non zero rc, matikan saja kunci dari save_status.


1

Saya membutuhkan ini, tetapi proses target bukan anak dari shell saat ini, dalam hal wait $PIDini tidak berfungsi. Saya memang menemukan alternatif berikut:

while [ -e /proc/$PID ]; do sleep 0.1 ; done

Itu bergantung pada keberadaan procfs , yang mungkin tidak tersedia (misalnya Mac tidak menyediakannya). Jadi untuk portabilitas, Anda bisa menggunakan ini:

while ps -p $PID >/dev/null ; do sleep 0.1 ; done

1

Menjebak sinyal CHLD mungkin tidak berfungsi karena Anda dapat kehilangan beberapa sinyal jika tiba secara bersamaan.

#!/bin/bash

trap 'rm -f $tmpfile' EXIT

tmpfile=$(mktemp)

doCalculations() {
    echo start job $i...
    sleep $((RANDOM % 5)) 
    echo ...end job $i
    exit $((RANDOM % 10))
}

number_of_jobs=10

for i in $( seq 1 $number_of_jobs )
do
    ( trap "echo job$i : exit value : \$? >> $tmpfile" EXIT; doCalculations ) &
done

wait 

i=0
while read res; do
    echo "$res"
    let i++
done < "$tmpfile"

echo $i jobs done !!!

1

solusi untuk menunggu beberapa subproses dan keluar ketika salah satu dari mereka keluar dengan kode status tidak nol adalah dengan menggunakan 'tunggu -n'

#!/bin/bash
wait_for_pids()
{
    for (( i = 1; i <= $#; i++ )) do
        wait -n $@
        status=$?
        echo "received status: "$status
        if [ $status -ne 0 ] && [ $status -ne 127 ]; then
            exit 1
        fi
    done
}

sleep_for_10()
{
    sleep 10
    exit 10
}

sleep_for_20()
{
    sleep 20
}

sleep_for_10 &
pid1=$!

sleep_for_20 &
pid2=$!

wait_for_pids $pid2 $pid1

kode status '127' adalah untuk proses yang tidak ada yang berarti anak mungkin telah keluar.


1

Tunggu semua pekerjaan dan kembalikan kode keluar dari pekerjaan gagal terakhir. Tidak seperti solusi di atas, ini tidak memerlukan penghematan pid. Hanya bg pergi, dan tunggu.

function wait_ex {
    # this waits for all jobs and returns the exit code of the last failing job
    ecode=0
    while true; do
        wait -n
        err="$?"
        [ "$err" == "127" ] && break
        [ "$err" != "0" ] && ecode="$err"
    done
    return $ecode
}

Ini akan bekerja dan secara andal memberikan kode kesalahan pertama dari perintah Anda yang dijalankan kecuali jika itu terjadi "perintah tidak ditemukan" (kode 127).
drevicko

0

Mungkin ada kasus di mana proses selesai sebelum menunggu proses. Jika kami memicu menunggu proses yang sudah selesai, itu akan memicu kesalahan seperti pid bukan anak dari shell ini. Untuk menghindari kasus seperti itu, fungsi berikut dapat digunakan untuk menemukan apakah prosesnya selesai atau tidak:

isProcessComplete(){
PID=$1
while [ -e /proc/$PID ]
do
    echo "Process: $PID is still running"
    sleep 5
done
echo "Process $PID has finished"
}

0

Saya pikir cara paling lurus ke depan untuk menjalankan pekerjaan secara paralel dan memeriksa status menggunakan file sementara. Sudah ada beberapa jawaban yang sama (misalnya Nietzche-jou dan mug896).

#!/bin/bash
rm -f fail
for i in `seq 0 9`; do
  doCalculations $i || touch fail &
done
wait 
! [ -f fail ]

Kode di atas bukan thread aman. Jika Anda khawatir kode di atas akan berjalan bersamaan dengan itu, lebih baik menggunakan nama file yang lebih unik, seperti gagal. $$. Baris terakhir adalah untuk memenuhi persyaratan: "kembalikan kode 1 ketika salah satu dari subproses berakhir dengan kode! = 0?" Saya melemparkan persyaratan tambahan di sana untuk membersihkan. Mungkin lebih jelas untuk menulisnya seperti ini:

#!/bin/bash
trap 'rm -f fail.$$' EXIT
for i in `seq 0 9`; do
  doCalculations $i || touch fail.$$ &
done
wait 
! [ -f fail.$$ ] 

Berikut ini adalah cuplikan yang serupa untuk mengumpulkan hasil dari beberapa pekerjaan: Saya membuat direktori sementara, cerita output dari semua tugas sub dalam file terpisah, dan kemudian membuangnya untuk ditinjau. Ini tidak cocok dengan pertanyaan - saya berikan sebagai bonus:

#!/bin/bash
trap 'rm -fr $WORK' EXIT

WORK=/tmp/$$.work
mkdir -p $WORK
cd $WORK

for i in `seq 0 9`; do
  doCalculations $i >$i.result &
done
wait 
grep $ *  # display the results with filenames and contents

0

Saya hampir jatuh ke dalam perangkap menggunakan jobs -puntuk mengumpulkan PID, yang tidak berfungsi jika anak sudah keluar, seperti yang ditunjukkan dalam skrip di bawah ini. Solusi yang saya ambil hanyalah menelepon wait -nN kali, di mana N adalah jumlah anak yang saya miliki, yang kebetulan saya kenal secara deterministik.

#!/usr/bin/env bash

sleeper() {
    echo "Sleeper $1"
    sleep $2
    echo "Exiting $1"
    return $3
}

start_sleepers() {
    sleeper 1 1 0 &
    sleeper 2 2 $1 &
    sleeper 3 5 0 &
    sleeper 4 6 0 &
    sleep 4
}

echo "Using jobs"
start_sleepers 1

pids=( $(jobs -p) )

echo "PIDS: ${pids[*]}"

for pid in "${pids[@]}"; do
    wait "$pid"
    echo "Exit code $?"
done

echo "Clearing other children"
wait -n; echo "Exit code $?"
wait -n; echo "Exit code $?"

echo "Waiting for N processes"
start_sleepers 2

for ignored in $(seq 1 4); do
    wait -n
    echo "Exit code $?"
done

Keluaran:

Using jobs
Sleeper 1
Sleeper 2
Sleeper 3
Sleeper 4
Exiting 1
Exiting 2
PIDS: 56496 56497
Exiting 3
Exit code 0
Exiting 4
Exit code 0
Clearing other children
Exit code 0
Exit code 1
Waiting for N processes
Sleeper 1
Sleeper 2
Sleeper 3
Sleeper 4
Exiting 1
Exiting 2
Exit code 0
Exit code 2
Exiting 3
Exit code 0
Exiting 4
Exit code 0
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.