Làm thế nào để đợi trong bash để một số quy trình con kết thúc và trả về mã thoát! = 0 khi bất kỳ quy trình con nào kết thúc bằng mã! = 0?


562

Làm cách nào để đợi trong tập lệnh bash cho một số quy trình con được sinh ra từ tập lệnh đó để hoàn tất và trả về mã thoát! = 0 khi bất kỳ quy trình con nào kết thúc bằng mã! = 0?

Kịch bản đơn giản:

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

Kịch bản trên sẽ chờ tất cả 10 quy trình được sinh ra, nhưng nó sẽ luôn đưa ra trạng thái thoát 0 (xem help wait). Làm cách nào tôi có thể sửa đổi tập lệnh này để nó sẽ phát hiện trạng thái thoát của các quy trình con được sinh ra và trả về mã thoát 1 khi bất kỳ quy trình con nào kết thúc bằng mã! = 0?

Có giải pháp nào tốt hơn cho việc đó ngoài việc thu thập các PID của các quy trình con, chờ chúng theo thứ tự và tổng các trạng thái thoát?


1
Điều này có thể được cải thiện đáng kể để chạm vào wait -n, có sẵn trong bash hiện đại để chỉ quay lại khi lệnh đầu tiên / tiếp theo hoàn thành.
Charles Duffy

nếu bạn đang muốn thử nghiệm bằng Bash, hãy thử điều này: github.com/sstephenson/bats
Alexander Mills

2
Sự phát triển tích cực của BATS đã chuyển sang github.com/bats-core/bats-core
Potherca

3
@CharlesDuffy wait -ncó một vấn đề nhỏ: nếu không còn công việc con nào (còn gọi là điều kiện chủng tộc), nó sẽ trả về trạng thái thoát không bằng 0 (không thành công) có thể không thể phân biệt được với quy trình con không thành công.
drevicko

5
@CharlesDuffy - Bạn có cái nhìn sâu sắc tuyệt vời và bạn thực hiện một dịch vụ lớn cho SO bằng cách chia sẻ nó. Dường như khoảng 80% bài viết SO tôi đọc có bạn chia sẻ những viên kim cương kiến ​​thức nhỏ tuyệt vời trong các bình luận phải đến từ một đại dương kinh nghiệm rộng lớn. Cảm ơn nhiều!
Brett Holman

Câu trả lời:


520

waitcũng (tùy chọn) lấy PID của quá trình để chờ và với $! bạn nhận được lệnh PID của lệnh cuối cùng được khởi chạy trong nền. Sửa đổi vòng lặp để lưu trữ PID của từng quy trình con được sinh ra thành một mảng, sau đó lặp lại chờ đợi trên mỗi 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, vì bạn sẽ đợi tất cả các quy trình không thành vấn đề nếu vd: bạn đang đợi ở lần đầu tiên trong khi lần thứ hai đã kết thúc (lần thứ 2 sẽ được chọn ở lần lặp tiếp theo). Đó là cách tiếp cận tương tự mà bạn sử dụng trong C với sự chờ đợi (2).
Luca Tettamanti

7
À, tôi hiểu rồi - diễn giải khác nhau :) Tôi đọc câu hỏi có nghĩa là "trả lại mã thoát 1 ngay lập tức khi bất kỳ quy trình con nào thoát".
Alnitak

56
PID có thể được sử dụng lại thực sự, nhưng bạn không thể chờ đợi một quy trình không phải là con của quy trình hiện tại (chờ thất bại trong trường hợp đó).
tkokoszka

12
Bạn cũng có thể sử dụng% n để chỉ công việc nền n: và %% để chỉ công việc gần đây nhất.
Conny

30
@Nils_M: Bạn nói đúng, tôi xin lỗi. Vì vậy, nó sẽ là một cái gì đó như: for i in $n_procs; do ./procs[${i}] & ; pids[${i}]=$!; done; for pid in ${pids[*]}; do wait $pid; done;phải không?
đồng bộ

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đang đưa ra các PID của các quy trình con đang ở trạng thái thực thi. Nó sẽ bỏ qua một quá trình nếu quá trình kết thúc trước jobs -pđược gọi. Vì vậy, nếu bất kỳ quy trình con nào kết thúc trước đó jobs -p, trạng thái thoát của quy trình đó sẽ bị mất.
tkokoszka

15
Wow, câu trả lời này là cách tốt hơn so với đánh giá hàng đầu. : /
e40

4
@ e40 và câu trả lời dưới đây có lẽ còn tốt hơn. Và thậm chí tốt hơn có thể là chạy từng lệnh với '(cmd; echo "$?" >> "$ tmpfile"), sử dụng sự chờ đợi này và sau đó đọc tệp cho thất bại. Cũng chú thích đầu ra. Chỉ hoặc sử dụng tập lệnh này khi bạn không quan tâm đến điều đó.
HoverHell

Tôi muốn nói thêm rằng câu trả lời này tốt hơn câu trả lời được chấp nhận
shurikk

2
@tkokoszka để được chính xác jobs -plà không cho PID của trình con, nhưng thay vào đó GPIDs . Logic chờ đợi dường như vẫn hoạt động, nó luôn chờ đợi nhóm nếu nhóm đó tồn tại và pid nếu không, nhưng thật tốt khi nhận ra .. đặc biệt là nếu người ta xây dựng dựa trên điều này và kết hợp một cái gì đó như gửi tin nhắn đến quy trình con trong đó trường hợp cú pháp khác nhau tùy thuộc vào việc bạn có PID hay GPID .. tức là kill -- -$GPIDso vớikill $PID
Timo

58

Dưới đây là ví dụ đơn giản sử dụng wait.

Chạy một số quy trình:

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

Sau đó chờ đợi họ với waitlệnh:

$ wait < <(jobs -p)

Hoặc chỉ wait(không có đối số) cho tất cả.

Điều này sẽ chờ cho tất cả các công việc trong nền được hoàn thành.

Nếu -ntùy chọn được cung cấp, đợi công việc tiếp theo chấm dứt và trả về trạng thái thoát của nó.

Xem: help waithelp jobscho cú pháp.

Tuy nhiên, nhược điểm là điều này sẽ chỉ trả về trạng thái của ID cuối cùng, vì vậy bạn cần kiểm tra trạng thái cho từng quy trình con và lưu trữ nó trong biến.

Hoặc thực hiện chức năng tính toán của bạn để tạo một số tệp bị lỗi (trống hoặc với nhật ký thất bại), sau đó kiểm tra tệp đó nếu tồn tại, ví dụ:

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

1
Đối với những người mới sử dụng bash, hai phép tính trong ví dụ ở đây là sleep 20 && truesleep 20 && false- tức là: thay thế các phép tính bằng (các) hàm của bạn. Để hiểu &&||, hãy chạy man bashvà nhập '/' (tìm kiếm) rồi '^ * Lists' (một biểu thức chính) rồi nhập: người đàn ông sẽ cuộn xuống phần mô tả &&||
drevicko

1
bạn có thể nên kiểm tra xem tập tin 'fail' không tồn tại khi bắt đầu (hoặc xóa nó). Tùy thuộc vào ứng dụng, bạn cũng nên thêm '2> & 1' trước khi ||bắt STDERR không thành công.
drevicko

tôi thích cái này, có nhược điểm nào không? thực ra, chỉ khi tôi muốn liệt kê tất cả các quy trình con và thực hiện một số hành động, vd. gửi tín hiệu, rằng tôi sẽ cố gắng ghi sổ sổ sách hoặc lặp đi lặp lại công việc. Đợi kết thúc, chỉ cầnwait
xgwang

Điều này sẽ bỏ lỡ trạng thái thoát công việc thất bại trước khi công việc -p được gọi
Erik Aronesty

50

Nếu bạn đã cài đặt GNU Parallel, bạn có thể làm:

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

GNU Parallel sẽ cung cấp cho bạn mã thoát:

  • 0 - Tất cả các công việc chạy không có lỗi.

  • 1-253 - Một số công việc thất bại. Trạng thái thoát cho số lượng công việc thất bại

  • 254 - Hơn 253 việc làm thất bại.

  • 255 - Lỗi khác.

Xem video giới thiệu để tìm hiểu thêm: http://pi.dk/1


1
Cảm ơn! Nhưng bạn đã quên đề cập đến vấn đề "nhầm lẫn" mà sau đó tôi đã rơi vào: unix.stackexchange.com/a353953
nobar

1
Đây trông giống như một công cụ tuyệt vời, nhưng tôi không nghĩ các công cụ trên hoạt động như trong một tập lệnh Bash trong đó doCalculationscó một hàm được định nghĩa trong cùng tập lệnh đó (mặc dù OP không rõ ràng về yêu cầu này). Khi tôi thử, parallelnói /bin/bash: doCalculations: command not found(nó nói điều này 10 lần cho seq 0 9ví dụ trên). Xem ở đây để giải quyết.
tộc

3
Cũng đáng quan tâm: xargscó một số khả năng để khởi động các công việc song song thông qua -Ptùy chọn. Từ đây : export -f doCalculations ; seq 0 9 |xargs -P 0 -n 1 -I{} bash -c "doCalculations {}". Hạn chế xargsđược liệt kê trong trang man cho parallel.
tộc

Và nếu doCalculationsphụ thuộc vào bất kỳ biến môi trường nội bộ tập lệnh nào khác (tùy chỉnh PATH, v.v.), có lẽ chúng cần phải được chỉnh sửa rõ ràng exporttrước khi khởi chạy parallel.
tộc

4
@nobar Sự nhầm lẫn là do một số người đóng gói làm rối tung mọi thứ cho người dùng của họ. Nếu bạn cài đặt bằng cách sử dụng, wget -O - pi.dk/3 | shbạn sẽ không nhận được nhầm lẫn. Nếu trình đóng gói của bạn làm hỏng mọi thứ cho bạn, tôi khuyến khích bạn nêu vấn đề với trình đóng gói của bạn. Các biến và hàm nên được xuất (export -f) cho GNU Parallel để xem chúng (xem man parallel: gnu.org/software/abul/ trộm )
Ole Tange

46

Làm thế nào về đơn giản:

#!/bin/bash

pids=""

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

wait $pids

...code continued here ...

Cập nhật:

Như được chỉ ra bởi nhiều người bình luận, phần trên chờ đợi tất cả các quy trình được hoàn thành trước khi tiếp tục, nhưng không thoát và thất bại nếu một trong số chúng thất bại, có thể thực hiện với sửa đổi sau được đề xuất bởi @Bryan, @SamBrightman và các quy trình khác :

#!/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
Theo các trang chờ, chờ với nhiều PID chỉ trả về giá trị trả về của quy trình cuối cùng được chờ. Vì vậy, bạn cần một vòng lặp bổ sung và chờ đợi từng PID riêng biệt, như được đề xuất trong câu trả lời được chấp nhận (trong các bình luận).
Vlad Frolov

1
Bởi vì nó dường như không được nêu ở bất kỳ nơi nào khác trên trang này, tôi sẽ thêm rằng vòng lặp sẽ làfor pid in $pids; do wait $pid; done
Bryan

1
@bisounours_tronconneuse vâng, bạn có. Xem help wait- với nhiều ID chỉ waittrả về mã thoát của mã cuối cùng, như @ vlad-frolov đã nói ở trên.
Sam Brightman

1
Bryan, @SamBrightman Ok. Tôi đã sửa đổi nó với các đề xuất của bạn.
patapouf_ai

4
Tôi có một mối quan tâm rõ ràng với giải pháp này: nếu một quy trình nhất định thoát ra trước khi tương ứng waitđược gọi thì sao? Hóa ra đây không phải là vấn đề: nếu bạn waittrên một quy trình đã thoát, waitsẽ thoát ngay lập tức với trạng thái của quy trình đã thoát. (Cảm ơn bạn, bashcác tác giả!)
Daniel Griscom

39

Đây là những gì tôi đã đưa ra cho đến nay. Tôi muốn xem làm thế nào để làm gián đoạn lệnh ngủ nếu một đứa trẻ chấm dứt, để người ta không phải điều chỉnh WAITALL_DELAYviệc sử dụng của một người.

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

Người ta có thể bỏ qua WAITALL_DELAY hoặc đặt nó ở mức rất thấp, vì không có quy trình nào được bắt đầu trong vòng lặp Tôi không nghĩ nó quá đắt.
Mary

21

Để song song điều này ...

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

Dịch nó sang ...

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 {}
      }' 
   )
  • Nếu xảy ra lỗi trong một quy trình, nó sẽ không làm gián đoạn các quy trình khác, nhưng nó sẽ dẫn đến một mã thoát khác không từ toàn bộ chuỗi .
  • Xuất các hàm và biến có thể hoặc không cần thiết, trong mọi trường hợp cụ thể.
  • Bạn có thể đặt --max-procsdựa trên mức độ song song mà bạn muốn ( 0có nghĩa là "tất cả cùng một lúc").
  • GNU Parallel cung cấp một số tính năng bổ sung khi được sử dụng thay thế xargs- nhưng nó không phải lúc nào cũng được cài đặt theo mặc định.
  • Các forvòng lặp là không thực sự cần thiết trong ví dụ này kể từ khi echo $iđược về cơ bản chỉ là tái tạo đầu ra của $(whatever_list). Tôi chỉ nghĩ rằng việc sử dụng fortừ khóa làm cho nó dễ dàng hơn một chút để xem những gì đang xảy ra.
  • Xử lý chuỗi Bash có thể gây nhầm lẫn - Tôi đã thấy rằng sử dụng các trích dẫn đơn hoạt động tốt nhất để gói các tập lệnh không tầm thường.
  • Bạn có thể dễ dàng làm gián đoạn toàn bộ hoạt động (sử dụng ^ C hoặc tương tự), không giống như cách tiếp cận trực tiếp hơn với song song Bash .

Đây là một ví dụ đơn giản hóa ...

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


7

Tôi không tin điều đó là có thể với chức năng dựng sẵn của Bash.

Bạn có thể nhận được thông báo khi một đứa trẻ thoát ra:

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

Tuy nhiên, không có cách rõ ràng nào để có được trạng thái thoát của trẻ em trong trình xử lý tín hiệu.

Có được trạng thái con đó thường là công việc của waithọ các hàm trong API POSIX cấp thấp hơn. Thật không may, sự hỗ trợ của Bash cho điều đó bị hạn chế - bạn có thể đợi một quy trình con cụ thể (và nhận trạng thái thoát của nó) hoặc bạn có thể đợi tất cả chúng và luôn nhận được kết quả 0.

Những gì nó dường như không thể làm là tương đương waitpid(-1), sẽ chặn cho đến khi bất kỳ tiến trình con nào trả về.


7

Tôi thấy rất nhiều ví dụ hay được liệt kê ở đây, cũng muốn ném tôi vào.

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

Tôi sử dụng một cái gì đó rất giống với việc khởi động / dừng máy chủ / dịch vụ song song và kiểm tra từng trạng thái thoát. Làm việc tuyệt vời cho tôi. Mong rằng nó giúp ai đó thoát!


Khi tôi dừng nó với Ctrl + CI vẫn thấy các tiến trình đang chạy trong nền.
karsten

2
@karsten - đây là một vấn đề khác. Giả sử bạn đang sử dụng bash, bạn có thể bẫy một điều kiện thoát (bao gồm Ctrl + C) và có các quy trình con hiện tại và tất cả bị giết bằng cách sử dụngtrap "kill 0" EXIT
Phil

@Phil đúng. Vì đây là các quy trình nền, việc giết tiến trình cha chỉ để lại bất kỳ tiến trình con nào đang chạy. Ví dụ của tôi không bẫy bất kỳ tín hiệu nào, có thể được thêm vào nếu cần thiết như Phil đã nêu.
Jason Slobotski

6

Đây là thứ mà tôi sử dụng:

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

5

Đoạn mã sau sẽ chờ hoàn thành tất cả các tính toán và trả về trạng thái thoát 1 nếu bất kỳ doCalculations nào thất bại.

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

5

Chỉ cần lưu trữ kết quả ra khỏi shell, ví dụ như trong một tệp.

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

Đây là phiên bản của tôi hoạt động cho nhiều pids, ghi lại các cảnh báo nếu quá trình thực thi mất quá nhiều thời gian và dừng các quy trình con nếu việc thực thi mất nhiều thời gian hơn một giá trị nhất định.

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
}

Ví dụ, đợi cho cả ba quá trình kết thúc, ghi lại cảnh báo nếu việc thực thi mất loger hơn 5 giây, dừng tất cả các quy trình nếu việc thực thi mất hơn 120 giây. Đừng thoát chương trình về những thất bại.

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

Nếu bạn có sẵn bash 4.2 trở lên, những điều sau đây có thể hữu ích cho bạn. Nó sử dụng các mảng kết hợp để lưu trữ các tên tác vụ và "mã" của chúng cũng như các tên tác vụ và các giá trị của chúng. Tôi cũng đã xây dựng một phương pháp giới hạn tỷ lệ đơn giản, có thể có ích nếu các tác vụ của bạn tiêu tốn nhiều thời gian CPU hoặc I / O và bạn muốn giới hạn số lượng tác vụ đồng thời.

Kịch bản khởi chạy tất cả các tác vụ trong vòng lặp đầu tiên và tiêu thụ kết quả trong vòng thứ hai.

Đây là một chút quá mức cho các trường hợp đơn giản nhưng nó cho phép các công cụ khá gọn gàng. Ví dụ, người ta có thể lưu trữ các thông báo lỗi cho từng tác vụ trong một mảng kết hợp khác và in chúng sau khi mọi thứ đã ổn định.

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

Tôi vừa sửa đổi một kịch bản thành nền và song song một quá trình.

Tôi đã thực hiện một số thử nghiệm (trên Solaris với cả bash và ksh) và phát hiện ra rằng 'chờ' xuất ra trạng thái thoát nếu nó không bằng 0 hoặc danh sách các công việc trả về thoát không bằng 0 khi không cung cấp đối số PID. Ví dụ

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

Đầu ra này được ghi vào stderr, vì vậy một giải pháp đơn giản cho ví dụ OP có thể là:

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

Trong khi điều này:

wait 2> >(wc -l)

cũng sẽ trả về một số đếm nhưng không có tệp tmp. Điều này cũng có thể được sử dụng theo cách này, ví dụ:

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

Nhưng điều này không hữu ích hơn nhiều so với tệp tmp IMO. Tôi không thể tìm thấy một cách hữu ích để tránh tệp tmp trong khi cũng tránh chạy "chờ" trong một mạng con, không hoạt động gì cả.


3

Tôi đã thực hiện điều này và kết hợp tất cả các phần tốt nhất từ ​​các ví dụ khác ở đây. Kịch bản lệnh này sẽ thực thi checkpidschức năng khi bất kỳ quá trình nền nào thoát ra và xuất trạng thái thoát mà không cần dùng đến việc bỏ phiếu.

#!/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 cho phép bạn sử dụng fg & bg trong một tập lệnh
  • fg, ngoài việc đặt tiến trình cuối cùng vào nền trước, còn có trạng thái thoát giống như quá trình mà nó tiến hành
  • while fgsẽ dừng vòng lặp khi bất kỳ fglối thoát nào có trạng thái thoát khác không

thật không may, điều này sẽ không xử lý trường hợp khi một quá trình trong nền thoát ra với trạng thái thoát khác không. (vòng lặp sẽ không chấm dứt ngay lập tức. Nó sẽ đợi các quá trình trước hoàn thành.)


3

Đã có rất nhiều câu trả lời ở đây, nhưng tôi ngạc nhiên dường như không ai đề nghị sử dụng mảng ... Vì vậy, đây là những gì tôi đã làm - điều này có thể hữu ích với một số người trong tương lai.

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

Điều này hoạt động, sẽ tốt như vậy nếu không tốt hơn câu trả lời của @ 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

và tất nhiên, tôi đã bất tử tập lệnh này, trong một dự án NPM cho phép bạn chạy các lệnh bash song song, hữu ích để thử nghiệm:

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


trap $? $$dường như đặt mã thoát thành 0 và PID thành shell bash đang chạy hiện tại, mọi lúc đối với tôi
inetknght 7/07/17

bạn hoàn toàn chắc chắn về điều đó? Không chắc chắn nếu điều đó có ý nghĩa.
Alexander Mills

2

bẫy là bạn của bạn Bạn có thể bẫy ERR trong rất nhiều hệ thống. Bạn có thể bẫy EXIT hoặc trên DEBUG để thực hiện một đoạn mã sau mỗi lệnh.

Điều này ngoài tất cả các tín hiệu tiêu chuẩn.


1
Xin vui lòng bạn có thể xây dựng câu trả lời của bạn với một số ví dụ.
Σοδε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đầu làm cho kịch bản của bạn dừng lại ở thất bại.

expectsẽ trở lại 1nếu bất kỳ subjob không thành công.


2

Chính xác cho mục đích này, tôi đã viết một bashchức năng được gọi là :for.

Lưu ý : :forkhông chỉ bảo tồn và trả về mã thoát của hàm bị lỗi mà còn chấm dứt tất cả các trường hợp chạy song song. Mà có thể không cần thiết trong trường hợp này.

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

sử dụng

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

Người giới thiệu


1

Tôi đã sử dụng gần đây (nhờ 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

Từ đó, người ta có thể dễ dàng ngoại suy và kích hoạt (chạm vào tệp, gửi tín hiệu) và thay đổi tiêu chí đếm (đếm tệp được chạm hoặc bất cứ thứ gì) để đáp ứng với kích hoạt đó. Hoặc nếu bạn chỉ muốn 'bất kỳ' không phải RC, chỉ cần giết khóa từ save_status.


1

Tôi cần điều này, nhưng quá trình mục tiêu không phải là con của lớp vỏ hiện tại, trong trường hợp wait $PIDđó không hoạt động. Tôi đã tìm thấy sự thay thế sau đây:

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

Điều đó phụ thuộc vào sự hiện diện của Procfs , có thể không có sẵn (ví dụ Mac không cung cấp nó). Vì vậy, đối với tính di động, bạn có thể sử dụng điều này thay thế:

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

1

Bẫy tín hiệu CHLD có thể không hoạt động vì bạn có thể mất một số tín hiệu nếu chúng đến cùng một lúc.

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

giải pháp để chờ một số quy trình con và thoát khi bất kỳ một trong số chúng thoát ra với mã trạng thái khác không là bằng cách sử dụng '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

mã trạng thái '127' dành cho quy trình không tồn tại, có nghĩa là trẻ có thể đã thoát.


1

Đợi tất cả các công việc và trả lại mã thoát của công việc thất bại cuối cùng. Không giống như các giải pháp ở trên, điều này không yêu cầu tiết kiệm pid. Chỉ cần bg đi, và chờ đợi.

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
}

Điều này sẽ hoạt động và đáng tin cậy đưa ra mã lỗi đầu tiên từ các lệnh được thực thi của bạn trừ khi nó không phải là "lệnh không tìm thấy" (mã 127).
drevicko

0

Có thể có một trường hợp quá trình hoàn tất trước khi chờ quá trình. Nếu chúng ta kích hoạt chờ đợi một quá trình đã kết thúc, nó sẽ gây ra lỗi như pid không phải là con của shell này. Để tránh những trường hợp như vậy, chức năng sau đây có thể được sử dụng để tìm xem liệu quy trình đã hoàn thành hay chưa:

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

0

Tôi nghĩ rằng cách đơn giản nhất để chạy các công việc song song và kiểm tra trạng thái là sử dụng các tệp tạm thời. Đã có một vài câu trả lời tương tự (ví dụ Nietzche-jou và Mug896).

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

Các mã trên không phải là chủ đề an toàn. Nếu bạn lo ngại rằng mã ở trên sẽ chạy cùng lúc với chính nó, tốt hơn là sử dụng tên tệp độc đáo hơn, như fail. $$. Dòng cuối cùng là để đáp ứng yêu cầu: "trả về mã thoát 1 khi bất kỳ quy trình con nào kết thúc bằng mã! = 0?" Tôi đã ném một yêu cầu thêm trong đó để làm sạch. Nó có thể đã rõ ràng hơn để viết nó như thế này:

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

Đây là một đoạn tương tự để thu thập kết quả từ nhiều công việc: Tôi tạo một thư mục tạm thời, kể chuyện đầu ra của tất cả các tác vụ phụ trong một tệp riêng biệt, sau đó kết xuất chúng để xem xét. Điều này không thực sự phù hợp với câu hỏi - Tôi đang ném nó như một phần thưởng:

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

Tôi gần như rơi vào cái bẫy của việc sử dụng jobs -pđể thu thập các PID, nó không hoạt động nếu đứa trẻ đã thoát ra, như trong kịch bản dưới đây. Giải pháp tôi chọn chỉ đơn giản là gọi wait -nN lần, trong đó N là số con tôi có, điều mà tôi tình cờ biết.

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

Đầu ra:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.