วิธีรอใน bash เพื่อให้หลาย ๆ กระบวนการย่อยเสร็จสิ้นและกลับรหัสทางออก! = 0 เมื่อกระบวนการย่อยใด ๆ จบลงด้วยรหัส! = 0


562

จะรอสคริปต์ bash สำหรับหลาย ๆ subprocesses ที่เกิดจาก script นั้นเพื่อเสร็จสิ้นและส่งกลับ code exit! = 0 เมื่อกระบวนการย่อยใด ๆ จบลงด้วย code! = 0?

สคริปต์ง่าย ๆ :

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

สคริปต์ด้านบนจะรอการประมวลผลย่อยทั้งหมด 10 รายการ แต่จะให้สถานะการออก 0 เสมอ (ดูhelp wait) ฉันจะแก้ไขสคริปต์นี้ได้อย่างไรเพื่อให้ค้นพบสถานะการออกของโพรเซสย่อยที่เกิดและกลับรหัสทางออก 1 เมื่อโพรเซสใด ๆ ที่ลงท้ายด้วยรหัส! = 0?

มีวิธีแก้ปัญหาที่ดีกว่าการรวบรวม PID ของกระบวนการย่อยรอให้อยู่ในลำดับและสถานะทางออกรวมหรือไม่


1
สิ่งนี้อาจได้รับการปรับปรุงให้ดีขึ้นอย่างมีนัยสำคัญเพื่อสัมผัสบนwait -nมีอยู่ใน bash ทันสมัยเพื่อกลับเฉพาะเมื่อคำสั่งแรก / ถัดไปเสร็จสมบูรณ์
Charles Duffy

หากคุณต้องการทดสอบการใช้ Bash ลองใช้วิธีนี้: github.com/sstephenson/bats
Alexander Mills

2
การพัฒนาที่ใช้งานของ BATS ได้ย้ายไปที่github.com/bats-core/bats-core
Potherca

3
@CharlesDuffy wait -nมีปัญหาเล็ก ๆ น้อยหนึ่ง: หากไม่มีงานย่อยที่เหลืออยู่ (สภาพการแข่งขัน) จะส่งคืนสถานะทางออกที่ไม่ใช่ศูนย์ (ล้มเหลว) ซึ่งสามารถแยกไม่ออกจากกระบวนการย่อยที่ล้มเหลว
drevicko

5
@CharlesDuffy - คุณมีข้อมูลเชิงลึกที่ยอดเยี่ยมและคุณให้บริการที่ยอดเยี่ยมเพื่อแบ่งปันให้ ดูเหมือนว่าประมาณ 80% ของโพสต์ SO ที่ฉันอ่านได้ให้คุณแบ่งปันความรู้เล็ก ๆ น้อย ๆ ในความคิดเห็นที่ต้องมาจากมหาสมุทรอันกว้างใหญ่แห่งประสบการณ์ ขอบคุณมาก!
Brett Holman

คำตอบ:


520

waitยัง (เป็นทางเลือก) ใช้ PID ของกระบวนการเพื่อรอและด้วย $! คุณได้รับ PID ของคำสั่งสุดท้ายที่เปิดใช้ในพื้นหลัง ปรับเปลี่ยนการวนซ้ำเพื่อเก็บ PID ของแต่ละกระบวนการย่อยที่เกิดในอาร์เรย์แล้ววนซ้ำอีกครั้งรอแต่ละ 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 เนื่องจากคุณจะรอกระบวนการทั้งหมดไม่สำคัญว่าเช่นคุณกำลังรอกระบวนการแรกในขณะที่กระบวนการที่สองเสร็จสิ้นลงแล้ว มันเป็นวิธีการเดียวกับที่คุณใช้ใน C พร้อมกับรอ (2)
Luca Tettamanti

7
อ่าฉันเข้าใจ - การตีความที่แตกต่างกัน :) ฉันอ่านคำถามในความหมายว่า "return exit code 1 ทันทีเมื่อใด ๆ ของ subprocesses exit"
Alnitak

56
PID อาจถูกนำกลับมาใช้จริง แต่คุณไม่สามารถรอกระบวนการที่ไม่ใช่ลูกของกระบวนการปัจจุบันได้ (การรอล้มเหลวในกรณีนั้น)
tkokoszka

12
คุณยังสามารถใช้% n เพื่ออ้างถึงงานพื้นหลัง n: th และ %% เพื่ออ้างถึงงานล่าสุด
conny

30
@Nils_M: คุณพูดถูกฉันขอโทษ ดังนั้นมันจะเป็นเช่น: for i in $n_procs; do ./procs[${i}] & ; pids[${i}]=$!; done; for pid in ${pids[*]}; do wait $pid; done;ใช่ไหม
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 -pกำลังให้ PID ของกระบวนการย่อยที่อยู่ในสถานะดำเนินการ มันจะข้ามกระบวนการหากกระบวนการเสร็จสิ้นก่อนที่jobs -pจะเรียกว่า ดังนั้นหากกระบวนการย่อยใดสิ้นสุดลงก่อนjobs -pสถานะการออกของกระบวนการนั้นจะหายไป
tkokoszka

15
ว้าวคำตอบนี้ดีกว่าคำตอบที่ได้คะแนนสูงสุด : /
e40

4
@ e40 และคำตอบด้านล่างน่าจะดียิ่งขึ้น และอาจจะดีกว่าถ้าจะเรียกใช้แต่ละคำสั่งด้วย '(cmd; echo "$?" >> "$ tmpfile") ใช้การรอนี้แล้วอ่านไฟล์สำหรับความล้มเหลว นอกจากนี้ยังมีคำอธิบายประกอบเอาท์พุท ... หรือเพียงแค่ใช้สคริปต์นี้เมื่อคุณไม่สนใจมาก
HoverHell

ฉันต้องการเพิ่มว่าคำตอบนี้ดีกว่าที่ยอมรับ
shurikk

2
@tkokoszka มีความถูกต้องjobs -pจะไม่ให้PIDsของกระบวนการย่อย แต่แทนที่จะGPIDs ตรรกะการรอคอยดูเหมือนว่าจะทำงานอยู่แล้วมันมักจะรออยู่ในกลุ่มหากกลุ่มดังกล่าวมีอยู่และ pid ถ้าไม่ แต่ก็ควรระวัง .. โดยเฉพาะอย่างยิ่งหากมีใครสร้างมันขึ้นมาและรวมเอาบางอย่างเช่นการส่งข้อความ กรณีไวยากรณ์แตกต่างกันไปขึ้นอยู่กับว่าคุณมี PID หรือ GPIDs .. เช่นkill -- -$GPIDvskill $PID
Timo

58

waitนี่คือตัวอย่างง่ายๆโดยใช้

เรียกใช้กระบวนการบางอย่าง:

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

จากนั้นรอพวกเขาด้วยwaitคำสั่ง:

$ wait < <(jobs -p)

หรือเพียงแค่wait(ไม่มีข้อโต้แย้ง) สำหรับทุกคน

การดำเนินการนี้จะรอให้งานทั้งหมดในพื้นหลังเสร็จสมบูรณ์

หาก-nมีการจัดหาตัวเลือกให้รองานต่อไปเพื่อยกเลิกและส่งคืนสถานะการออก

ดู: help waitและhelp jobsสำหรับไวยากรณ์

อย่างไรก็ตามข้อเสียคือสิ่งนี้จะส่งคืนสถานะ ID ล่าสุดเท่านั้นดังนั้นคุณต้องตรวจสอบสถานะสำหรับแต่ละกระบวนการย่อยและเก็บไว้ในตัวแปร

หรือทำให้ฟังก์ชั่นการคำนวณของคุณสร้างไฟล์บางไฟล์เมื่อเกิดข้อผิดพลาด (ว่างเปล่าหรือมีไฟล์บันทึกล้มเหลว) จากนั้นตรวจสอบไฟล์นั้นถ้ามีอยู่เช่น

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

1
สำหรับผู้ที่เพิ่งจะทุบตีการคำนวณทั้งสองในตัวอย่างที่นี่sleep 20 && trueและsleep 20 && false- เช่น: แทนที่สิ่งเหล่านั้นด้วยฟังก์ชัน เพื่อให้เข้าใจ&&และ||เรียกใช้man bashและพิมพ์ '/' (ค้นหา) จากนั้น '^ * รายการ' (regex) จากนั้นป้อน: มนุษย์จะเลื่อนลงไปที่คำอธิบายของ&&และ||
drevicko

1
คุณควรตรวจสอบว่าไฟล์ 'ล้มเหลว' ไม่มีอยู่ในตอนเริ่มต้น (หรือลบทิ้ง) ทั้งนี้ขึ้นอยู่กับแอปพลิเคชันมันอาจเป็นความคิดที่ดีที่จะเพิ่ม '2> & 1' ก่อนที่||จะจับ STDERR ในความล้มเหลวเช่นกัน
drevicko

ฉันชอบอันนี้ข้อเสียใด ๆ จริง ๆ แล้วเฉพาะเมื่อฉันต้องการแสดงรายการกระบวนการย่อยทั้งหมดและดำเนินการบางอย่างเช่น ส่งสัญญาณว่าฉันจะพยายามทำบัญชี pids หรือทำซ้ำงาน รอให้เสร็จสิ้นเพียงwait
xgwang

สิ่งนี้จะพลาดสถานะการออกของงานที่ล้มเหลวก่อนงาน -p ถูกเรียกว่า
Erik Aronesty

50

หากคุณมี GNU Parallel ติดตั้งอยู่คุณสามารถทำได้:

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

GNU Parallel จะให้รหัสออกกับคุณ:

  • 0 - งานทั้งหมดรันโดยไม่มีข้อผิดพลาด

  • 1-253 - งานบางงานล้มเหลว สถานะการออกให้จำนวนงานที่ล้มเหลว

  • 254 - งานมากกว่า 253 งานล้มเหลว

  • 255 - ข้อผิดพลาดอื่น ๆ

ดูวิดีโอแนะนำเพื่อเรียนรู้เพิ่มเติม: http://pi.dk/1


1
ขอบคุณ! แต่คุณลืมที่จะพูดถึงปัญหา "ความสับสน" ซึ่งฉันก็ตกหลุม: unix.stackexchange.com/a/35953
nobar

1
ดูเหมือนว่าเป็นเครื่องมือที่ยอดเยี่ยม แต่ฉันไม่คิดว่าการทำงานด้านบนจะเป็นในสคริปต์ Bash ซึ่งdoCalculationsเป็นฟังก์ชันที่กำหนดไว้ในสคริปต์เดียวกันนั้น (แม้ว่า OP ไม่ชัดเจนเกี่ยวกับข้อกำหนดนี้) เมื่อฉันลองparallelพูดว่า/bin/bash: doCalculations: command not found(มันบอกว่า 10 ครั้งสำหรับseq 0 9ตัวอย่างด้านบน) ดูที่นี่สำหรับการแก้ปัญหา
nobar

3
สิ่งที่น่าสนใจ: xargsมีความสามารถในการเปิดงานพร้อมกันผ่าน-Pตัวเลือก จากที่นี่ : export -f doCalculations ; seq 0 9 |xargs -P 0 -n 1 -I{} bash -c "doCalculations {}". ข้อ จำกัด ของการมีระบุไว้ในหน้าคนสำหรับxargs parallel
nobar

และถ้าdoCalculationsอาศัยอยู่กับตัวแปรอื่น ๆ สคริปต์ภายในสภาพแวดล้อม (กำหนดเองPATHฯลฯ ) พวกเขาอาจจำเป็นต้องได้รับอย่างชัดเจนexported parallelก่อนที่จะเปิดตัว
สูงศักดิ์

4
@nobar ความสับสนนั้นเกิดจากผู้ทำบรรจุภัณฑ์ทำให้ผู้ใช้สับสน หากคุณติดตั้งโดยใช้wget -O - pi.dk/3 | shคุณจะไม่สับสน หากหีบห่อของคุณทำสิ่งที่ยุ่งเหยิงสำหรับคุณฉันขอแนะนำให้คุณแจ้งปัญหากับผู้ทำแพ็กเกจของคุณ ตัวแปรและฟังก์ชั่นควรส่งออก (export -f) สำหรับ GNU Parallel เพื่อดูพวกมัน (ดูman parallel: gnu.org/software/parallel/ … )
Ole Tange

46

ง่าย ๆ เพียงแค่:

#!/bin/bash

pids=""

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

wait $pids

...code continued here ...

ปรับปรุง:

ดังที่มีผู้แสดงความคิดเห็นหลายคนกล่าวไว้ข้างต้นรอให้กระบวนการทั้งหมดเสร็จสิ้นก่อนที่จะดำเนินการต่อ แต่ไม่ออกและล้มเหลวหากมีข้อใดข้อหนึ่งล้มเหลวสามารถทำการแก้ไขดังต่อไปนี้ที่ @Bryan, @SamBrightman :

#!/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
ตามหน้ารอหน้าคนรอด้วย PID หลายรายการเท่านั้นที่จะส่งคืนค่าส่งคืนของกระบวนการสุดท้ายที่รอ ดังนั้นคุณต้องมีการวนซ้ำเพิ่มเติมและรอ PID แยกกันตามคำแนะนำในคำตอบที่ยอมรับ (ในความคิดเห็น)
Vlad Frolov

1
เนื่องจากดูเหมือนว่าไม่ได้มีการระบุที่อื่นใดในหน้านี้ฉันจะเพิ่มว่าการวนซ้ำจะเป็นอย่างไรfor pid in $pids; do wait $pid; done
Bryan

1
@bisounours_tronconneuse ใช่คุณทำ โปรดดูhelp wait- ด้วย ID หลายรหัสwaitจะส่งคืนรหัสทางออกของรหัสสุดท้ายเท่านั้นตามที่ @ vlad-frolov กล่าวข้างต้น
Sam Brightman

1
ไบรอัน @SamBrightman ตกลง ฉันแก้ไขด้วยคำแนะนำของคุณ
patapouf_ai

4
ฉันมีความกังวลอย่างชัดเจนเกี่ยวกับวิธีแก้ปัญหานี้: จะเกิดอะไรขึ้นถ้ากระบวนการที่กำหนดให้ออกไปก่อนที่waitจะมีการเรียกที่สอดคล้องกัน? ปรากฎว่านี่ไม่ใช่ปัญหา: หากคุณwaitอยู่ในกระบวนการที่ออกไปแล้วwaitจะออกจากทันทีด้วยสถานะของกระบวนการที่ออกไปแล้ว (ขอขอบคุณbashผู้เขียน!)
Daniel Griscom

39

นี่คือสิ่งที่ฉันเกิดขึ้น ฉันต้องการดูวิธีการขัดจังหวะคำสั่ง sleep หากเด็กยุติการใช้งานเพื่อที่จะไม่ต้องปรับWAITALL_DELAYการใช้งาน

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

หนึ่งอาจข้าม WAITALL_DELAY นั้นหรือตั้งค่าต่ำมากเนื่องจากไม่มีกระบวนการใดเริ่มต้นในลูปฉันไม่คิดว่ามันแพงเกินไป
Marian

21

เพื่อขนานนี้ ...

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

แปลเป็น ...

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 {}
      }' 
   )
  • หากมีข้อผิดพลาดเกิดขึ้นในกระบวนการหนึ่งมันจะไม่ขัดจังหวะกระบวนการอื่น แต่จะส่งผลให้รหัสออกที่ไม่เป็นศูนย์จากลำดับโดยรวมมันจะส่งผลในการที่ไม่ใช่ศูนย์รหัสทางออกจากลำดับในภาพรวม
  • ฟังก์ชั่นการส่งออกและตัวแปรอาจหรือไม่จำเป็นในบางกรณี
  • คุณสามารถตั้งค่า--max-procsตามจำนวนความขนานที่คุณต้องการ ( 0หมายถึง "ทั้งหมดในครั้งเดียว")
  • GNU Parallelเสนอคุณสมบัติเพิ่มเติมบางอย่างเมื่อใช้แทนxargs - แต่จะไม่ติดตั้งตามค่าเริ่มต้นเสมอไป
  • forห่วงไม่จำเป็นอย่างเคร่งครัดในตัวอย่างนี้ตั้งแต่echo $iเป็นเพียงการปฏิรูปการส่งออกของ$(whatever_list) ฉันแค่คิดว่าการใช้forคำหลักทำให้ง่ายขึ้นเล็กน้อยในการดูว่าเกิดอะไรขึ้น
  • การจัดการสตริงของ Bash อาจทำให้เกิดความสับสน - ฉันพบว่าการใช้เครื่องหมายคำพูดเดี่ยวเหมาะสำหรับการตัดสคริปต์ที่ไม่สำคัญ
  • คุณสามารถขัดจังหวะการดำเนินการทั้งหมด (โดยใช้ ^ C หรือคล้ายกัน) ซึ่งแตกต่างจากวิธีการโดยตรงมากขึ้นในการทุบตีขนาน

นี่คือตัวอย่างการทำงานที่เรียบง่าย ...

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


7

ฉันไม่เชื่อว่าเป็นไปได้ด้วยฟังก์ชั่นการใช้งานของ Bash

คุณสามารถรับการแจ้งเตือนเมื่อมีเด็กออก:

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

อย่างไรก็ตามไม่มีวิธีที่ชัดเจนในการรับสถานะการออกของเด็กในตัวจัดการสัญญาณ

การได้รับสถานะลูกนั้นมักจะเป็นงานของwaitตระกูลฟังก์ชันใน POSIX API ระดับต่ำกว่า น่าเสียดายที่การสนับสนุนของ Bash นั้นมี จำกัด - คุณสามารถรอกระบวนการลูกที่หนึ่ง (และรับสถานะการออก) หรือคุณสามารถรอให้พวกเขาทั้งหมดและได้รับผล 0 เสมอ

อะไรมันจะปรากฏขึ้นเป็นไปไม่ได้ที่จะทำคือเทียบเท่าwaitpid(-1)ซึ่งบล็อกจนกว่าใด ๆผลตอบแทนกระบวนการเด็ก


7

ฉันเห็นตัวอย่างที่ดีมากมายที่ระบุไว้ที่นี่ต้องการที่จะโยนของฉันเช่นกัน

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

ฉันใช้สิ่งที่คล้ายกันมากในการเริ่ม / หยุดเซิร์ฟเวอร์ / บริการพร้อมกันและตรวจสอบสถานะการออกแต่ละครั้ง ใช้งานได้ดีสำหรับฉัน หวังว่านี่จะช่วยให้ใครบางคนออกไป!


เมื่อฉันหยุดด้วย Ctrl + CI ยังคงเห็นกระบวนการทำงานในพื้นหลัง
karsten

2
@karsten - นี่เป็นปัญหาที่แตกต่าง สมมติว่าคุณกำลังใช้ bash คุณสามารถดักจับเงื่อนไขการออก (รวมถึง Ctrl + C) และทำให้กระบวนการลูกทั้งหมดถูกฆ่าโดยใช้trap "kill 0" EXIT
Phil

@Phil ถูกต้อง เนื่องจากสิ่งเหล่านี้เป็นกระบวนการพื้นหลังการฆ่ากระบวนการหลักจะทำให้กระบวนการลูก ๆ ทำงาน ตัวอย่างของฉันไม่ดักจับสัญญาณใด ๆ ซึ่งสามารถเพิ่มได้ถ้าจำเป็นตามที่ฟิลได้กล่าวไว้
Jason Slobotski


5

รหัสต่อไปนี้จะรอให้เสร็จสิ้นทั้งหมดของการคำนวณและการกลับมาออกจากสถานะ 1 ถ้าใด ๆ ของdoCalculationsล้มเหลว

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

5

เพียงเก็บผลลัพธ์จากเปลือกเช่นในไฟล์

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

นี่คือเวอร์ชันของฉันที่ใช้สำหรับหลาย pids บันทึกคำเตือนหากการดำเนินการใช้เวลานานเกินไปและหยุดกระบวนการย่อยหากการดำเนินการใช้เวลานานกว่าค่าที่กำหนด

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
}

ตัวอย่างรอให้กระบวนการทั้งสามเสร็จสิ้นบันทึกคำเตือนหากการประมวลผลใช้เวลานานกว่า 5 วินาทีหยุดกระบวนการทั้งหมดหากการประมวลผลใช้เวลานานกว่า 120 วินาที อย่าออกจากโปรแกรมเมื่อเกิดความล้มเหลว

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

หากคุณมีทุบตี 4.2 หรือใหม่กว่าพร้อมใช้งานต่อไปนี้อาจเป็นประโยชน์กับคุณ จะใช้อาร์เรย์ที่เชื่อมโยงเพื่อจัดเก็บชื่องานและ "รหัส" ของพวกเขาเช่นเดียวกับชื่องานและ pids ของพวกเขา ฉันได้สร้างวิธีการ จำกัด อัตราง่ายๆซึ่งอาจมีประโยชน์หากงานของคุณใช้เวลา CPU หรือ I / O จำนวนมากและคุณต้องการ จำกัด จำนวนงานที่เกิดขึ้นพร้อมกัน

สคริปต์เรียกใช้งานทั้งหมดในลูปแรกและใช้ผลลัพธ์ในภารกิจที่สอง

นี่เป็นเรื่องที่เกินความจริงเล็กน้อยสำหรับกรณีง่าย ๆ ตัวอย่างเช่นหนึ่งสามารถเก็บข้อความผิดพลาดสำหรับแต่ละงานในอาเรย์เชื่อมโยงอื่นและพิมพ์พวกเขาหลังจากทุกอย่างได้ตัดสินลง

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

ฉันเพิ่งได้รับการแก้ไขสคริปต์ให้เป็นพื้นหลังและกระบวนการคู่ขนาน

ฉันทำการทดลองบางอย่าง (บน Solaris ที่มีทั้ง bash และ ksh) และพบว่า 'รอ' แสดงสถานะทางออกหากไม่เป็นศูนย์หรือรายการงานที่ส่งคืนการออกที่ไม่เป็นศูนย์เมื่อไม่มีการระบุอาร์กิวเมนต์ PID เช่น

ทุบตี:

$ 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

เอาต์พุตนี้เขียนไปยัง stderr ดังนั้นวิธีแก้ไขปัญหาอย่างง่ายสำหรับตัวอย่าง OPs อาจเป็น:

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

ในขณะนี้:

wait 2> >(wc -l)

จะส่งกลับการนับ แต่ไม่มีไฟล์ tmp อาจใช้วิธีนี้เช่น:

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

แต่นี่ไม่ได้มีประโยชน์อะไรมากไปกว่าไฟล์ tmp IMO ฉันไม่พบวิธีที่มีประโยชน์ในการหลีกเลี่ยงไฟล์ tmp ในขณะที่ยังหลีกเลี่ยงการเรียกใช้ "รอ" ใน subshell ซึ่งจะไม่ทำงานเลย


3

ฉันเคยไปที่นี้และรวมส่วนที่ดีที่สุดทั้งหมดจากตัวอย่างอื่น ๆ ที่นี่ สคริปต์นี้จะเรียกใช้checkpidsฟังก์ชันเมื่อกระบวนการพื้นหลังใด ๆออกและส่งออกสถานะการออกโดยไม่ต้องหันไปใช้การเลือกตั้ง

#!/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 ช่วยให้คุณใช้ fg & bg ในสคริปต์
  • fgนอกเหนือจากการวางกระบวนการสุดท้ายในเบื้องหน้ามีสถานะการออกเช่นเดียวกับกระบวนการที่ทำงานเบื้องหน้า
  • while fgจะหยุดการวนซ้ำเมื่อfgออกจากสถานะการออกที่ไม่ใช่ศูนย์

โชคไม่ดีที่กรณีนี้จะไม่จัดการกรณีที่กระบวนการในพื้นหลังออกจากด้วยสถานะออกที่ไม่ใช่ศูนย์ (การวนซ้ำจะไม่ยุติทันทีมันจะรอให้กระบวนการก่อนหน้านี้เสร็จสิ้น)


3

มีคำตอบมากมายอยู่แล้วที่นี่ แต่ฉันแปลกใจที่ไม่มีใครแนะนำให้ใช้อาร์เรย์ ... ดังนั้นนี่คือสิ่งที่ฉันทำ - นี่อาจเป็นประโยชน์กับบางคนในอนาคต

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

ผลงานนี้ควรจะดีถ้าไม่ดีกว่าคำตอบของ @ 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

และแน่นอนฉันได้ทำให้เป็นอมตะสคริปต์นี้ในโครงการ NPM ซึ่งช่วยให้คุณสามารถรันคำสั่ง bash ในแบบขนานซึ่งมีประโยชน์สำหรับการทดสอบ:

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


trap $? $$ดูเหมือนว่าจะออกจากรหัสชุด 0 และ PID เปลือกทำงานทุบตีปัจจุบันทุกครั้งสำหรับฉัน
inetknght

คุณแน่ใจอย่างนั้นหรือ ไม่แน่ใจว่าเหมาะสมหรือไม่
Alexander Mills

2

กับดักคือเพื่อนของคุณ คุณสามารถดักจับ ERR ในหลาย ๆ ระบบ คุณสามารถดัก EXIT หรือบน DEBUG เพื่อดำเนินการกับส่วนของรหัสหลังจากทุกคำสั่ง

นอกจากสัญญาณมาตรฐานทั้งหมดแล้ว


1
กรุณาอธิบายรายละเอียดด้วยตัวอย่าง
Ϲοδε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

set -eที่ด้านบนทำให้หยุดสคริปต์ของคุณในความล้มเหลว

expectจะกลับมา1หาก subjob ล้มเหลว


2

ว่าเพื่อวัตถุประสงค์นี้ผมเขียนฟังก์ชั่นที่เรียกว่าbash:for

หมายเหตุ : :forไม่เพียงรักษาและส่งคืนรหัสออกของฟังก์ชั่นที่ล้มเหลว แต่ยังยุติอินสแตนซ์ที่ทำงานแบบขนานทั้งหมด ซึ่งอาจไม่จำเป็นในกรณีนี้

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

การใช้

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

อ้างอิง


1

ฉันใช้สิ่งนี้เมื่อเร็ว ๆ นี้ (ขอบคุณ 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

จากที่นั่นหนึ่งสามารถคาดการณ์ได้อย่างง่ายดายและมีทริกเกอร์ (สัมผัสไฟล์ส่งสัญญาณ) และเปลี่ยนเกณฑ์การนับ (สัมผัสนับไฟล์สัมผัสหรืออะไรก็ตาม) เพื่อตอบสนองต่อทริกเกอร์ที่ หรือถ้าคุณต้องการ 'ที่' ไม่ใช่ศูนย์ rc เพียงฆ่าล็อคจาก save_status


1

ฉันต้องการสิ่งนี้ แต่กระบวนการเป้าหมายไม่ใช่ลูกของเชลล์ปัจจุบันซึ่งในกรณีนี้ใช้wait $PIDไม่ได้ ฉันหาทางเลือกต่อไปนี้แทน:

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

ที่ขึ้นอยู่กับการปรากฏตัวของprocfsซึ่งอาจไม่สามารถใช้ได้ (Mac ไม่ได้ให้ตัวอย่าง) ดังนั้นสำหรับการพกพาคุณสามารถใช้สิ่งนี้แทน:

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

1

การดักสัญญาณ CHLD อาจไม่ทำงานเพราะคุณอาจสูญเสียสัญญาณบางอย่างหากสัญญาณมาพร้อมกัน

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

วิธีแก้ปัญหาเพื่อรอการประมวลผลย่อยหลายรายการและออกเมื่อมีรายการใดรายการหนึ่งออกมาพร้อมกับรหัสสถานะที่ไม่เป็นศูนย์โดยใช้ 'wait -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

รหัสสถานะ '127' สำหรับกระบวนการที่ไม่มีอยู่ซึ่งหมายความว่าเด็กอาจออก


1

รองานทั้งหมดและส่งคืนรหัสทางออกของงานที่ล้มเหลวครั้งสุดท้าย ซึ่งแตกต่างจากการแก้ปัญหาข้างต้นนี้ไม่จำเป็นต้องประหยัด pid เพิ่งไปแล้วรอ

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
}

สิ่งนี้จะทำงานและให้รหัสข้อผิดพลาดแรกจากคำสั่งที่ดำเนินการของคุณอย่างน่าเชื่อถือเว้นแต่จะเป็น "ไม่พบคำสั่ง" (รหัส 127)
drevicko

0

อาจมีกรณีที่กระบวนการเสร็จสมบูรณ์ก่อนที่จะรอให้กระบวนการ ถ้าเราทริกเกอร์รอให้กระบวนการที่เสร็จสิ้นไปแล้วมันจะทริกเกอร์ข้อผิดพลาดเช่น pid ไม่ใช่ลูกของเชลล์นี้ เพื่อหลีกเลี่ยงกรณีดังกล่าวสามารถใช้ฟังก์ชันต่อไปนี้เพื่อค้นหาว่ากระบวนการเสร็จสมบูรณ์หรือไม่:

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

0

ฉันคิดว่าวิธีที่ตรงไปตรงมาที่สุดในการทำงานแบบขนานและตรวจสอบสถานะคือการใช้ไฟล์ชั่วคราว มีคำตอบที่คล้ายกันสองสามข้อ (เช่น Nietzche-jou และ mug896)

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

รหัสข้างต้นไม่ปลอดภัยกระทู้ หากคุณกังวลว่ารหัสข้างต้นจะทำงานพร้อมกันกับตัวเองมันจะดีกว่าถ้าใช้ชื่อไฟล์ที่ไม่ซ้ำกันมากขึ้นเช่นล้มเหลว $$. บรรทัดสุดท้ายคือปฏิบัติตามข้อกำหนด: "return exit code 1 เมื่อกระบวนการย่อยใด ๆ จบลงด้วยรหัส! = 0?" ฉันโยนความต้องการพิเศษในนั้นเพื่อทำความสะอาด มันอาจจะชัดเจนกว่าถ้าเขียนแบบนี้:

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

นี่คือตัวอย่างที่คล้ายกันสำหรับการรวบรวมผลลัพธ์จากงานหลายงาน: ฉันสร้างไดเรกทอรีชั่วคราวเล่าเรื่องผลลัพธ์ของงานย่อยทั้งหมดในไฟล์แยกต่างหากจากนั้นถ่ายโอนข้อมูลเพื่อตรวจสอบ สิ่งนี้ไม่ตรงกับคำถามจริงๆ - ฉันกำลังโยนมันเป็นโบนัส:

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

ฉันเกือบจะตกหลุมพรางของการใช้jobs -pเพื่อรวบรวม PID ซึ่งไม่ทำงานหากเด็กออกไปแล้วดังที่แสดงในสคริปต์ด้านล่าง วิธีแก้ปัญหาที่ฉันเลือกคือเพียงเรียกwait -nN ครั้งโดยที่ N คือจำนวนเด็กที่ฉันมีซึ่งฉันรู้ว่าเป็นการกำหนดอย่างไม่แน่นอน

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

เอาท์พุท:

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.