Herhangi bir alt işlem! = 0 ile sona erdiğinde çıkış kodunu! = 0 bitirmek ve döndürmek için birkaç alt işlemin bash'da nasıl beklenir?


562

Alt kodlardan herhangi biri! = 0 ile sona erdiğinde çıkış kodunu! = 0 bitirmek ve döndürmek için bu komut dosyasından çıkan birkaç alt işlem için bir bash komut dosyasında nasıl beklenir?

Basit komut dosyası:

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

Yukarıdaki komut dosyası, ortaya çıkan 10 alt işlemin tümünü bekleyecek, ancak her zaman 0 çıkış durumunu verecektir (bkz. help wait). Bu komut dosyasını, ortaya çıkan alt işlemlerin çıkış durumlarını keşfedecek ve alt işlemlerden herhangi biri! = 0 ile sona erdiğinde çıkış kodu 1'i döndürecek şekilde nasıl değiştirebilirim?

Bunun için alt süreçlerin PID'lerini toplamaktan, sırayla beklemekten ve çıkış durumlarını toplamaktan daha iyi bir çözüm var mı?


1
Bu, dokunmak için önemli ölçüde geliştirilebilir wait -n, sadece ilk / sonraki komut tamamlandığında dönmek için modern bash'de kullanılabilir.
Charles Duffy

Bash kullanarak test etmek istiyorsanız şunu deneyin: github.com/sstephenson/bats
Alexander Mills


3
@CharlesDuffy'nin wait -nküçük bir sorunu var: kalan alt iş yoksa (aka yarış durumu), başarısız bir alt işlemden ayırt edilemeyen sıfırdan farklı bir çıkış durumu (başarısız) döndürür.
drevicko

5
@CharlesDuffy - Harika bir görüşünüz var ve bunu paylaşarak SO'ya büyük bir hizmet veriyorsunuz. Okuduğum SO mesajlarının yaklaşık% 80'inin, geniş bir deneyim okyanusundan gelmesi gereken yorumlarda harika küçük bilgi elmaslarını paylaştığınız görülüyor. Çok teşekkürler!
Brett Holman

Yanıtlar:


520

waitayrıca (isteğe bağlı) beklemek için sürecin PID'sini alır ve $ ile! arka planda başlatılan son komutun PID'sini alırsınız. Dönen her alt işlemin PID'sini bir dizide saklamak için döngüyü değiştirin ve sonra her bir PID'de bekleyen döngüyü yeniden döngüye alın.

# 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, tüm süreçleri bekleyeceğiniz için, örneğin, ikincisi zaten bitmişken ilkini bekliyorsanız (2. bir sonraki yinelemede yine de seçilecek) önemli değil. (C) 'yi beklemede kullanacağınız yaklaşımın aynısı.
Luca Tettamanti

7
Ah, görüyorum - farklı yorum :) " Alt süreçlerden herhangi biri çıktığında hemen çıkış kodu 1'i geri dön" anlamını okudum .
Alnitak

56
PID gerçekten yeniden kullanılabilir, ancak geçerli işlemin alt öğesi olmayan bir işlemi bekleyemezsiniz (bu durumda bekleme başarısız olur).
tkokoszka

12
Ayrıca, n: th arka plandaki işe başvurmak için% n ve en sonuncusuna başvurmak için %% kullanabilirsiniz.
conny

30
@Nils_M: Haklısın, üzgünüm. Yani şöyle bir şey olurdu: for i in $n_procs; do ./procs[${i}] & ; pids[${i}]=$!; done; for pid in ${pids[*]}; do wait $pid; done;değil mi?
synack

284

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

103
jobs -pyürütme durumunda olan alt süreçlerin PID'lerini veriyor. İşlem daha önce tamamlanırsa bir işlem atlanır jobs -p. Dolayısıyla, alt işlemlerden herhangi biri daha önce sona ererse jobs -p, bu işlemin çıkış durumu kaybolur.
tkokoszka

15
Vay be, bu cevap en çok puan alandan çok daha iyi. : /
e40

4
@ e40 ve aşağıdaki cevap muhtemelen daha iyi. Ve daha da iyisi, her komutu '(cmd; echo "$?" >> "$ tmpfile") ile çalıştırmak, bu beklemeyi kullanmak ve daha sonra hatalar için dosyayı okumak olacaktır. Ayrıca not ekleyin. … Ya da çok fazla umursamadığınızda bu senaryoyu kullanın.
HoverHell

Bu cevabın kabul edilenden daha iyi olduğunu eklemek istiyorum
shurikk

2
@tkokoszka doğru olmak , alt süreçlerin PID'lerinijobs -p değil , GPID'leri vermektir . Bekleyen mantık yine de işe yarıyor gibi görünüyor, eğer böyle bir grup mevcutsa ve değilse pid varsa her zaman grubu bekler, ancak farkında olmak iyidir .. özellikle biri bunun üzerine inşa edilecek ve alt süreçlere mesaj göndermek gibi bir şey içeriyorsa vaka sözdizimi yani PID'ler veya GPIDs .. sahip olmasına bağlı olarak farklı vskill -- -$GPIDkill $PID
Timo

58

İşte basit bir örnek wait.

Bazı işlemleri çalıştırın:

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

Sonra waitkomutla onları bekleyin :

$ wait < <(jobs -p)

Ya da sadece waitherkes için (argüman olmadan).

Bu, arka plandaki tüm işlerin tamamlanmasını bekleyecektir.

Eğer -nseçenek verilir, bir sonraki iş için bekler sonlandırmak ve çıkış durumu ile döner için.

Sözdizimi için help waitve bölümüne bakınız help jobs.

Ancak olumsuz tarafı, bunun yalnızca son kimliğin durumuna geri döneceğidir, bu nedenle her alt işlemin durumunu kontrol etmeniz ve değişkende saklamanız gerekir.

Ya da hata durumunda (boş veya başarısız günlüğü ile) bir dosya oluşturmak için hesaplama işlevinizi yapın, ardından varsa dosyayı kontrol edin, örn.

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

1
Bash yeni olanlar için, örneğin iki hesaplamalar burada sleep 20 && trueve sleep 20 && false- yani: En fonksiyonu (ler) olanlar değiştirin. Anlamak &&ve ||, koşmak man bashve tip '/' (arama) daha sonra '^ * Listeleri' (regex) daha sonra girin: adam tanımına aşağı kayar &&ve||
drevicko

1
muhtemelen 'başarısız' dosyasının başlangıçta var olmadığını (veya sildiğini) kontrol etmelisiniz. Uygulamaya bağlı olarak, ||STDERR'ı yakalamak için başarısız olmadan önce '2> & 1' eklemek de iyi bir fikir olabilir .
drevicko

Bunu beğendim, herhangi bir dezavantaj? Aslında, sadece tüm alt süreci listelemek ve örneğin bazı eylemler yapmak istediğinizde. sinyal göndermek, ben defter tutma pids veya yineleme işleri çalışacağız. Bitirmek için bekleyin, sadecewait
xgwang

Bu, işler -p çağrılmadan önce başarısız olan işin çıkış durumunu özleyecektir
Erik Aronesty

50

Kurulu GNU Paralel varsa şunları yapabilirsiniz:

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

GNU Parallel size çıkış kodunu verecektir:

  • 0 - Tüm işler hatasız çalıştı.

  • 1-253 - Bazı işler başarısız oldu. Çıkış durumu başarısız işlerin sayısını verir

  • 254 - 253'ten fazla iş başarısız oldu.

  • 255 - Diğer hata.

Daha fazla bilgi edinmek için giriş videolarını izleyin: http://pi.dk/1


1
Teşekkürler! Ama sonradan içine düştüğüm
nobar

1
Bu harika bir araç gibi görünüyor, ama yukarıdaki gibi bir Bash komut dosyası olarak çalıştığını sanmıyorum doCalculations(burada OP bu gereksinim hakkında net olmasa da) aynı komut dosyasında tanımlanan bir işlevdir. Ben çalıştığınızda, paralleldiyor /bin/bash: doCalculations: command not found(onun için bu 10 kat diyor seq 0 9yukarıdaki örnekte). Geçici çözüm için buraya bakın .
nobar

3
Ayrıca ilgi çekici: seçeneği xargsparalel olarak iş başlatmak için bazı yeteneği vardır -P. Gönderen burada : export -f doCalculations ; seq 0 9 |xargs -P 0 -n 1 -I{} bash -c "doCalculations {}". İçin sınırlamalar xargskılavuz sayfasında numaralandırılmıştır parallel.
nobar

Ve doCalculationsbaşka herhangi bir komut dosyası iç ortam değişkenine (özel PATH, vb.) exportBağlıysa, başlatmadan önce muhtemelen açık bir şekilde düzenlenmesi gerekir parallel.
nobar

4
@nobar Karışıklık, bazı paketleyicilerin kullanıcıları için bir şeyler karıştırmasından kaynaklanıyor. Kullanarak yüklerseniz wget -O - pi.dk/3 | sh, karışıklık olmaz. Paketleyiciniz sizin için işleri karıştırdıysa, paketleyicinizle ilgili sorunu gidermenizi öneririm. Değişkenler ve işlevler, bunları görmek için GNU Paralel için dışa aktarılmalıdır (dışa aktarma -f) (bkz man parallel: gnu.org/software/parallel/… )
Ole Tange

46

Basitçe:

#!/bin/bash

pids=""

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

wait $pids

...code continued here ...

Güncelleme:

Birden fazla yorumcunun işaret ettiği gibi, yukarıdaki tüm süreçler devam etmeden önce tamamlanmasını bekler, ancak bunlardan biri başarısız olursa çıkmaz ve başarısız olmaz, @Bryan, @SamBrightman ve diğerleri tarafından önerilen aşağıdaki değişiklikle yapılabilir. :

#!/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
Bekleyen kılavuz sayfalarına göre, birden fazla PID ile beklemek, yalnızca beklenen son işlemin dönüş değerini döndürür. Bu nedenle, ekstra bir döngüye ihtiyacınız var ve kabul edilen yanıtta (yorumlarda) önerildiği gibi her bir PID'yi ayrı ayrı bekliyorsunuz.
Vlad Frolov

1
Bu sayfada başka bir yerde belirtilmemiş gibi göründüğü için, döngünün olacağını ekleyeceğimfor pid in $pids; do wait $pid; done
Bryan

1
@bisounours_tronconneuse evet, biliyorsun. Bkz. help wait- birden fazla kimlikle wait, yukarıda söylendiği gibi @ vlad-frolov'un yalnızca sonuncusunun çıkış kodunu döndürür.
Sam Brightman

1
Bryan, @SamBrightman Tamam. Tavsiyelerinizle değiştirdim.
patapouf_ai

4
Bu çözümle ilgili açık bir endişem vardı: belirli bir işlem karşılık gelen çağrıdan önce çıkarsa ne olur wait? Bunun bir sorun olmadığı ortaya çıkıyor: waitzaten çıkılmış bir işlemde, zaten çıkılmış olan işlemin waitdurumuyla hemen çıkacaktır. (Teşekkür ederim, bashyazarlar!)
Daniel Griscom

39

İşte şimdiye kadar bulduğum şey. Bir çocuk sonlandırılırsa uyku komutunun nasıl kesileceğini görmek istiyorum, böylece WAITALL_DELAYkişinin kullanımını ayarlaması gerekmez .

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

Muhtemelen WAITALL_DELAY'i atlayabilir veya çok düşük ayarlayabilir, döngü içinde hiçbir işlem başlatılmadığından çok pahalı olduğunu düşünmüyorum.
Marian

21

Bunu paralelleştirmek için ...

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

Bu dile çevir ...

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 {}
      }' 
   )
  • Bir işlemde bir hata oluşursa , diğer işlemleri kesintiye uğratmaz, ancak diziden bir bütün olarak sıfırdan farklı bir çıkış koduyla sonuçlanır .
  • Herhangi bir durumda işlevlerin ve değişkenlerin dışa aktarılması gerekli olabilir veya olmayabilir.
  • --max-procsNe kadar paralellik istediğinize göre ayarlayabilirsiniz ( 0"bir kerede" anlamına gelir).
  • GNU Parallel , yerine kullanıldığında bazı ek özellikler sunar xargs- ancak varsayılan olarak her zaman yüklenmez.
  • forÇünkü döngü bu örnekte kesinlikle gerekli değildir echo $itemelde sadece çıktısını yenileyici edilir $(whatever_list). Sadece foranahtar kelimenin kullanımının ne olduğunu görmeyi biraz kolaylaştırdığını düşünüyorum .
  • Bash string kullanımı kafa karıştırıcı olabilir - tek tırnak kullanmanın önemsiz olmayan komut dosyalarını kaydırmak için en iyi sonucu bulduğunu fark ettim.
  • Bash paralelliğine daha doğrudan yaklaşımın aksine , tüm işlemi (^ C veya benzeri kullanarak) kolayca kesebilirsiniz .

İşte basitleştirilmiş bir çalışma örneği ...

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


7

Bash'in yerleşik işlevselliği ile mümkün olduğuna inanmıyorum.

Sen edebilir bir çocuk çıktığında uyarı alacaksınız:

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

Bununla birlikte, sinyal işleyicisinde çocuğun çıkış durumunu almanın belirgin bir yolu yoktur.

Bu alt durumu almak genellikle waitalt düzey POSIX API'larındaki işlev ailesinin görevidir . Maalesef bunun için Bash'in destek sınırlıdır - sen bekleyebilir bir spesifik alt sürecin (ve çıkış durumu olsun) ya da bekleyebilir tüm bunların ve her zaman 0 sonuç almak.

Yapılması imkansız görünen şey, herhangi bir alt işlem dönene waitpid(-1)kadar engelleyen eşdeğerdir .


7

Burada listelenen birçok iyi örnek görüyorum, benimkini de atmak istedim.

#! /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

Sunucuları / hizmetleri paralel olarak başlatma / durdurmaya çok benzer bir şey kullanıyorum ve her çıkış durumunu kontrol ediyorum. Benim için harika çalışıyor. Umarım bu birisine yardım eder!


Ctrl + CI ile durdurduğumda hala arka planda çalışan işlemleri görüyorum.
karsten

2
@karsten - bu farklı bir sorun. Bash kullandığınızı varsayarsak, bir çıkış koşulu yakalayabilir (Ctrl + C dahil) ve şu anki ve tüm çocuk süreçlerini kullanarak öldürebilirsiniztrap "kill 0" EXIT
Phil

@Phil doğru. Bunlar arka plan işlemleri olduğundan, ana işlemi öldürmek tüm alt süreçleri çalışır durumda bırakır. Örneğim Phil'in belirttiği gibi gerekirse eklenebilecek sinyalleri yakalamıyor.
Jason Slobotski

6

Bu kullandığım bir şey:

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

5

Aşağıdaki kod, tüm hesaplamaların tamamlanmasını bekleyecek ve doCalculations'tan herhangi biri başarısız olursa , çıkış durumu 1'i döndürecektir .

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

5

Sonuçları kabuktan, örneğin bir dosyada saklamanız yeterlidir.

#!/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

Birden çok pids için çalışan sürümüm, yürütme çok uzun sürerse uyarıları günlüğe kaydeder ve yürütme belirli bir değerden daha uzun sürerse alt işlemleri durdurur.

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
}

Örneğin, her üç işlemin de bitmesini bekleyin, yürütme 5 saniyeden daha uzun sürerse bir uyarı günlüğü yapın, yürütme 120 saniyeden uzun sürerse tüm işlemleri durdurun. Hatalarda programdan çıkmayın.

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

Bash 4.2 veya daha yenisi varsa aşağıdakiler sizin için yararlı olabilir. Görev adlarını ve "kodlarını", ayrıca görev adlarını ve pidlerini depolamak için ilişkilendirilebilir diziler kullanır. Ayrıca, görevleriniz çok fazla CPU veya G / Ç zamanı tüketiyorsa ve aynı anda görevlerin sayısını sınırlamak istiyorsanız kullanışlı olabilecek basit bir hız sınırlama yöntemi oluşturdum.

Komut dosyası, ilk döngüdeki tüm görevleri başlatır ve ikincisinde sonuçları kullanır.

Bu basit vakalar için biraz fazladır, ancak oldukça düzgün şeyler sağlar. Örneğin, her görev için hata iletileri başka bir ilişkilendirilebilir dizide saklanabilir ve her şey çözüldükten sonra yazdırılabilir.

#! /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

Bir senaryoyu bir süreci arka planlamak ve paralel hale getirmek için değiştirdim.

Biraz deneme yaptım (hem bash hem de ksh ile Solaris'te) ve 'wait' değerinin, sıfır değilse çıkış durumunu veya PID argümanı sağlanmadığında sıfır olmayan çıkış döndüren işlerin bir listesini çıkardığını keşfettim. Örneğin

Bash:

$ 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

Bu çıktı stderr'a yazılır, bu nedenle OP örneğine basit bir çözüm şöyle olabilir:

#!/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

Bu arada:

wait 2> >(wc -l)

tmp dosyası olmadan bir sayı da döndürür. Bu da bu şekilde kullanılabilir, örneğin:

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

Ancak bu, tmp dosyası IMO'sundan çok daha kullanışlı değildir. Ben de hiç alışkanlık bir subshell, "beklemek" çalışan kaçınırken tmp dosya önlemek için yararlı bir yol bulamadık.


3

Buna bir göz attım ve buradaki diğer örneklerden en iyi parçaları birleştirdim. Bu komut dosyası, herhangi bir arka plan işlemi çıktığında checkpidsişlevi yürütür ve yoklama işlemine başvurmadan çıkış durumunu çıkarır.

#!/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 bir komut dosyasında fg & bg kullanmanızı sağlar
  • fg, son işlemi ön plana koymanın yanı sıra, ön plana çıkardığı işlemle aynı çıkış durumuna sahiptir.
  • while fgfgsıfır dışında çıkış durumuna sahip herhangi bir çıkış olduğunda döngüyü durdurur

maalesef bu, arka plandaki bir işlem sıfırdan farklı bir çıkış durumuyla çıktığında durumu ele almaz. (döngü hemen sonlandırılmaz. Önceki işlemlerin tamamlanmasını bekler.)


3

Burada zaten birçok cevap var, ama kimsenin dizileri kullanmayı önermediğine şaşırdım ... İşte yaptığım şey - bu gelecekte bazıları için yararlı olabilir.

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

Bu çalışır, @ HoverHell'in cevabından daha iyi olmasa bile iyi olmalıdır!

#!/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

ve elbette, bu komut dosyasını bash komutlarını paralel olarak çalıştırmanıza izin veren bir NPM projesinde ölümsüzleştirdim, test için yararlı:

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


trap $? $$Geçerli çalışan bash kabuğuna 0 ve PID set çıkış kodu gibi görünüyor, benim için her zaman
inetknght

Bundan kesinlikle emin misin? Bunun mantıklı olup olmadığından emin değilim.
Alexander Mills

2

tuzak senin arkadaşın. ERR'yi birçok sistemde yakalayabilirsiniz. Her komuttan sonra bir kod parçası gerçekleştirmek için EXIT veya DEBUG üzerinde yakalayabilirsiniz.

Bu, tüm standart sinyallere ek olarak.


1
Lütfen cevabınızı bazı örneklerle açıklayabilir misiniz?
Σοδε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

En set -eüstte komut dosyanız hata durumunda durur.

expect1herhangi bir subjob başarısız olursa dönecektir .


2

Tam olarak bu amaç için bir bashfonksiyon yazdım :for.

Not : :forsadece başarısız işlevin çıkış kodunu korumakla kalmaz, aynı zamanda tüm paralel çalışan örneği de sonlandırır. Bu durumda hangisi gerekli olmayabilir.

#!/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 $?
}

kullanım

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

Referanslar


1

Bunu son zamanlarda kullandım (Alnitak sayesinde):

#!/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

Buradan kolayca tahmin edilebilir ve bir tetikleyiciye (bir dosyaya dokunun, bir sinyal gönderin) ve bu tetikleyiciye yanıt vermek için sayım kriterlerini (dokunulan dosyaları sayın veya herhangi bir şekilde) değiştirin. Ya da sadece 'sıfır' olmayan bir rc istiyorsanız, save_status'tan kilidi öldürün.


1

Buna ihtiyacım vardı, ancak hedef süreç mevcut kabuğun bir çocuğu değildi, bu durumda wait $PIDişe yaramaz. Bunun yerine aşağıdaki alternatifi buldum:

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

Bu , mevcut olmayan procflerin varlığına dayanır (örneğin Mac bunu sağlamaz). Taşınabilirlik için bunun yerine şunu kullanabilirsiniz:

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

1

CHLD sinyalini yakalamak işe yaramayabilir, çünkü aynı anda geldiklerinde bazı sinyalleri kaybedebilirsiniz.

#!/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

birden fazla alt işlem beklemek ve bunlardan herhangi biri sıfır dışında durum kodu ile çıktığında çıkmak için çözüm 'wait -n' kullanmaktır

#!/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

durum kodu '127' var olmayan bir işlem içindir, yani çocuk çıkmış olabilir.


1

Tüm işleri bekleyin ve son başarısız olan işin çıkış kodunu döndürün. Yukarıdaki çözümlerden farklı olarak, bu, pid tasarrufu gerektirmez. Sadece uzak dur ve bekle.

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
}

Bu, "komut bulunamadı" (kod 127) olmadığı sürece çalıştırılan komutlarınızdan ilk hata kodunu çalıştıracak ve güvenilir bir şekilde verecektir.
drevicko

0

Süreci beklemeden önce sürecin tamamlandığı bir durum olabilir. Zaten bitmiş bir işlemi beklemeyi tetiklersek, pid bu kabuğun alt öğesi değil gibi bir hatayı tetikler. Bu gibi durumlardan kaçınmak için, işlemin tamamlanıp tamamlanmadığını bulmak için aşağıdaki işlev kullanılabilir:

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

0

İşleri paralel olarak çalıştırmanın ve durumu kontrol etmenin en basit yolunun geçici dosyalar kullanmak olduğunu düşünüyorum. Zaten birkaç benzer cevap var (örneğin, Nietzche-jou ve mug896).

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

Yukarıdaki kod iş parçacığı için güvenli değildir. Yukarıdaki kodun kendisiyle aynı anda çalışacağından endişe ediyorsanız, fail. $$ gibi daha benzersiz bir dosya adı kullanmak daha iyidir. Son satır şu şartı yerine getirmektir: "herhangi bir alt işlemden herhangi biri! = 0? Oraya temizlemek için ekstra bir gereksinim attım. Bunu şöyle yazmak daha açık olabilir:

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

Birden çok işten sonuç toplamak için benzer bir snippet: Geçici bir dizin oluşturuyorum, tüm alt görevlerin çıktılarını ayrı bir dosyada anlatıyorum ve daha sonra incelenmek üzere döküyorum. Bu gerçekten soruyla eşleşmiyor - bir bonus olarak atıyorum:

#!/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

Neredeyse jobs -paşağıdaki senaryoda gösterildiği gibi çocuk çıkmışsa işe yaramayan PID'leri toplamak için kullanma tuzağına düştüm . Seçtiğim çözüm sadece wait -nN kere çağırmaktı , burada N, sahip olduğum çocuk sayısıdır ve belirleyici olarak biliyordum.

#!/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

Çıktı:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.