Paralel arka plan işlemlerinin çıkış kodlarını toplayın (alt kabuklar)


18

Diyelim ki böyle bir senaryomuz var:

echo "x" &
echo "y" &
echo "z" &
.....
echo "Z" &
wait

alt kabukların / alt süreçlerin çıkış kodlarını toplamanın bir yolu var mı? Bunu yapmanın yolunu arıyor ve hiçbir şey bulamıyorum. Bu alt kabukları paralel olarak çalıştırmam gerekiyor, aksi takdirde evet bu daha kolay olurdu.

Ben genel bir çözüm arıyorum (ben paralel çalıştırmak için bilinmeyen / dinamik sayıda alt süreçleri var).


1
Ne istediğinizi bulmanızı ve sonra tam olarak aradığınız davranışı (belki de sözde kod veya daha büyük bir örnekle) net olmaya çalışarak yeni bir soru sormanızı önereceğim.
Michael Homer

3
Aslında sorunun şu anda iyi olduğunu düşünüyorum - dinamik alt süreçlerim var. Tüm çıkış kodlarını toplamam gerekiyor. Bu kadar.
Alexander Mills

Yanıtlar:


6

HandleJobs kullanan Alexander Mills'in cevabı bana harika bir başlangıç ​​noktası verdi, ama aynı zamanda bana bu hatayı verdi

uyarı: run_pending_traps: trap_list'deki hatalı değer [17]: 0x461010

Hangi bir bash yarış durumu sorunu olabilir

Bunun yerine her çocuğun pidini depoladım ve her çocuk için özel olarak çıkış kodunu bekledim ve aldım. Bu temizleyiciyi, alt süreçleri fonksiyonlarda ortaya çıkaran ve çocuk için beklemek istediğim bir ebeveyn sürecini beklemek riskinden kaçınarak buluyorum. Ne olduğu daha net, çünkü tuzağı kullanmıyor.

#!/usr/bin/env bash

# it seems it does not work well if using echo for function return value, and calling inside $() (is a subprocess spawned?) 
function wait_and_get_exit_codes() {
    children=("$@")
    EXIT_CODE=0
    for job in "${children[@]}"; do
       echo "PID => ${job}"
       CODE=0;
       wait ${job} || CODE=$?
       if [[ "${CODE}" != "0" ]]; then
           echo "At least one test failed with exit code => ${CODE}" ;
           EXIT_CODE=1;
       fi
   done
}

DIRN=$(dirname "$0");

commands=(
    "{ echo 'a'; exit 1; }"
    "{ echo 'b'; exit 0; }"
    "{ echo 'c'; exit 2; }"
    )

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

children_pids=()
for i in `seq 0 "$clen"`; do
    (echo "${commands[$i]}" | bash) &   # run the command via bash in subshell
    children_pids+=("$!")
    echo "$i ith command has been issued as a background job"
done
# wait; # wait for all subshells to finish - its still valid to wait for all jobs to finish, before processing any exit-codes if we wanted to
#EXIT_CODE=0;  # exit code of overall script
wait_and_get_exit_codes "${children_pids[@]}"

echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"
# end

havalı, sanırım netlik için for job in "${childen[@]}"; doolmalıfor job in "${1}"; do
Alexander Mills

Bu komut dosyası ile ilgili tek endişe children_pids+=("$!"), aslında alt kabuk için istenen pid yakalamak olduğunu.
Alexander Mills

1
"$ {1}" ile test ettim ve işe yaramıyor. Fonksiyona bir dizi geçiyorum ve görünüşe göre bu bash özel ilgi gerekiyor. , $! son ortaya çıkan işin pididir , bkz. tldp.org/LDP/abs/html/internalvariables.html Testlerimde düzgün çalışıyor gibi görünüyor ve şimdi unRAID cache_dirs komut dosyasında kullanıyorum ve öyle görünüyor onun işi. 4.4.12 bash kullanıyorum.
arberg

güzel yep doğru gibi görünüyor
Alexander Mills

20

Kullanım waitPID ile yapacaktır:

Her işlem kimliği pid veya iş belirtimi jobspec tarafından belirtilen alt işlemin çıkmasını bekleyin ve beklenen son komutun çıkış durumunu döndürün .

Her işlemin PID'sini kaydettiğinizde kaydetmeniz gerekir:

echo "x" & X=$!
echo "y" & Y=$!
echo "z" & Z=$!

Ayrıca komut dosyasında iş kontrolünü etkinleştirebilir set -mve bir %njobspec kullanabilirsiniz , ancak neredeyse kesinlikle istemezsiniz - iş kontrolünün başka birçok yan etkisi vardır .

waitişlemi biten işlemle aynı kodu döndürür. Sen kullanabilirsiniz wait $Xolarak nihai kod erişmek için herhangi bir (makul) sonraki bir noktada $?doğru / yanlış olarak ya da sadece kullanın:

echo "x" & X=$!
echo "y" & Y=$!
...
wait $X
echo "job X returned $?"

wait henüz tamamlanmamışsa komut tamamlanana kadar duraklar.

Böyle durmaktan kaçınmak istiyorsanız, bir trapaçık ayarlayabilirSIGCHLD , sonlandırma sayısını sayabilir ve tümwait hepsi bittikten sonra s'yi bir kerede . Muhtemelen waitneredeyse her zaman yalnız kullanmaktan kurtulabilirsiniz .


1
ughh, özür dilerim, bu alt kabukları paralel olarak çalıştırmam gerekiyor, bunu soruda belirteceğim ...
Alexander Mills

nevermind, belki bu benim kurulum ile çalışır ... wait komutu kodunuzda nerede devreye giriyor? Ben takip etmiyorum
Alexander Mills

1
Onlar @AlexanderMills edilir paralel çalışan. Değişken sayıda varsa, bir dizi kullanın. (örneğin burada bu şekilde bir kopya olabilir).
Michael Homer

cevabınıza bekleme komutu ilgilidir, daha sonra ekleyin lütfen eğer sayesinde evet, o kontrol edecek
Alexander Mills

Daha wait $Xsonraki herhangi bir noktada koşuyorsunuz .
Michael Homer

5

Komutları tanımlamak için iyi bir yolunuz varsa, çıkış kodlarını bir tmp dosyasına yazdırabilir ve sonra ilgilendiğiniz dosyaya erişebilirsiniz:

#!/bin/bash

for i in `seq 1 5`; do
    ( sleep $i ; echo $? > /tmp/cmd__${i} ) &
done

wait

for i in `seq 1 5`; do # or even /tmp/cmd__*
    echo "process $i:"
    cat /tmp/cmd__${i}
done

Tmp dosyalarını kaldırmayı unutmayın.


4

A compound command- ifadesini parantez içine alın:

( echo "x" ; echo X: $? ) &
( true ; echo TRUE: $? ) &
( false ; echo FALSE: $? ) &

çıktı verecek

x
X: 0
TRUE: 0
FALSE: 1

Birkaç komutu paralel olarak çalıştırmanın gerçekten farklı bir yolu GNU Parallel kullanmaktır . Çalıştırılacak komutların bir listesini yapın ve bunları dosyaya yerleştirin list:

cat > list
sleep 2 ; exit 7
sleep 3 ; exit 55
^D

Tüm komutları paralel olarak çalıştırın ve çıkış kodlarını dosyada toplayın job.log:

cat list | parallel -j0 --joblog job.log
cat job.log

ve çıktı:

Seq     Host    Starttime       JobRuntime      Send    Receive Exitval Signal  Command
1       :       1486892487.325       1.976      0       0       7       0       sleep 2 ; exit 7
2       :       1486892487.326       3.003      0       0       55      0       sleep 3 ; exit 55

tamam teşekkürler, bunu doğrulamanın bir yolu var mı? Sadece 3 alt süreçim yok, Z alt süreçlerim var.
Alexander Mills

Orijinal soruyu jenerik bir çözüm aradığımı yansıtacak şekilde güncelledim, teşekkürler
Alexander Mills

Bir döngü yapısını kullanmak için doğrulamanın bir yolu var mı?
Alexander Mills

Döngü? Sabit bir komut listeniz var mı veya kullanıcı tarafından kontrol ediliyor mu? Ne yapmaya çalıştığınızı anladığımdan emin değilim ama belki de PIPESTATUSkontrol etmeniz gereken bir şey. Bu seq 10 | gzip -c > seq.gz ; echo ${PIPESTATUS[@]}döndürür 0 0(ilk ve son komuttan çıkış kodu).
hschou

Evet temelde kullanıcı tarafından kontrol
Alexander Mills

2

bu, aradığınız genel komut dosyasıdır. Tek dezavantajı, komutlarınızın tırnak işaretleri içinde olması, yani IDE'niz aracılığıyla sözdizimi vurgulamanın gerçekten işe yaramayacağı anlamına gelir. Aksi takdirde, diğer cevapların birkaçını denedim ve bu en iyisi. Bu cevap, wait <pid>@Michael tarafından verilen kullanma fikrini içerir, ancak trapen iyi şekilde çalışan komutu kullanarak bir adım daha ileri gider .

#!/usr/bin/env bash

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

function handleJobs() {
     for job in `jobs -p`; do
         echo "PID => ${job}"
         CODE=0;
         wait ${job} || CODE=$?
         if [[ "${CODE}" != "0" ]]; then
         echo "At least one test failed with exit code => ${CODE}" ;
         EXIT_CODE=1;
         fi
     done
}

trap 'handleJobs' CHLD  # trap command is the key part
DIRN=$(dirname "$0");

commands=(
    "{ echo 'a'; exit 1; }"
    "{ echo 'b'; exit 0; }"
    "{ echo 'c'; exit 2; }"
)

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; # wait for all subshells to finish

echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"
# end

@michael homer sayesinde beni doğru yolda tuttuğunuz için, ama trapkomut kullanmak AFAICT için en iyi yaklaşımdır.


1
SIGCHLD tuzağını çocukları çıktıklarında işlemek için, o anda durumun yazdırılması gibi de kullanabilirsiniz. Veya bir ilerleme sayacını güncellemek: bir işlev bildirmek ve daha sonra "set -m" gibi etkileşimli olmayan bir kabukta açılması için bir seçenek gerektirse de "trap function_name CHLD" kullanın
13'te Chunko

1
Ayrıca "wait -n" herhangi bir çocuk için bekleyecek ve sonra o çocuğun çıkış durumunu $? değişken. Böylece her biri çıktıkça ilerlemeyi yazdırabilirsiniz. Bununla birlikte, CHLD tuzağını kullanmadığınız sürece, bu şekilde bazı çocuk çıkışlarını kaçırabileceğinizi unutmayın.
Chunko

@Chunko teşekkürler! bu iyi bilgi, belki cevabı en iyi olduğunu düşündüğünüz bir şeyle güncelleyebilir misiniz?
Alexander Mills

teşekkürler @Chunko, tuzak daha iyi çalışıyor, haklısın. Bekle <pid>, geçiş yaptım.
Alexander Mills

Tuzaklı versiyonun onsuz versiyondan nasıl ve neden daha iyi olduğuna inandığınızı açıklayabilir misiniz? (Bunun daha iyi olmadığına ve bu nedenle daha kötü olduğuna inanıyorum, çünkü faydası olmayan daha karmaşıktır.)
Scott

1

@Rolf'un cevabının başka bir varyasyonu:

Çıkış durumunu kaydetmenin başka bir yolu,

mkdir /tmp/status_dir

ve sonra her betiğe sahip olun

script_name="${0##*/}"  ## strip path from script name
tmpfile="/tmp/status_dir/${script_name}.$$"
do something
rc=$?
echo "$rc" > "$tmpfile"

Bu, onu oluşturan komut dosyasının adını ve işlem kimliğini (aynı komut dosyasının birden fazla örneğinin çalışması durumunda) daha sonra başvurmak üzere kaydedebileceğiniz ve tümünü tamamladığınızda alt dizinin tamamını silebilirsiniz.

Gibi bir şey yaparak her komut dosyasından birden fazla durum kaydedebilirsiniz

tmpfile="$(/bin/mktemp -q "/tmp/status_dir/${script_name}.$$.XXXXXX")"

ki bu dosyayı daha önce olduğu gibi oluşturur, ancak ona benzersiz bir rastgele dize ekler.

Veya aynı dosyaya daha fazla durum bilgisi ekleyebilirsiniz.


1

script3Yalnızca yürütülecek script1ve script2başarılı olduğunu ve script1ve script2paralel olarak yürütülecektir:

./script1 &
process1=$!

./script2 &
process2=$!

wait $process1
rc1=$?

wait $process2
rc2=$?

if [[ $rc1 -eq 0 ]] && [[ $rc2 -eq 0  ]];then
./script3
fi

AFAICT, bu Michael Homer'in cevabının yeniden canlandırılmasından başka bir şey değil .
Scott
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.