Thu thập mã thoát của các quá trình nền song song (vỏ phụ)


18

Nói rằng chúng tôi có một kịch bản bash như vậy:

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

Có cách nào để thu thập mã thoát của shell phụ / quy trình phụ không? Tìm cách để làm điều này và không thể tìm thấy bất cứ điều gì. Tôi cần chạy song song các subshells này, nếu không thì điều này sẽ dễ dàng hơn.

Tôi đang tìm kiếm một giải pháp chung (tôi có một số quy trình phụ chưa biết / động để chạy song song).


1
Tôi sẽ đề nghị bạn tìm ra những gì bạn muốn và sau đó hỏi một câu hỏi mới, cố gắng rõ ràng về chính xác hành vi bạn đang tìm kiếm (có thể với mã giả hoặc một ví dụ lớn hơn).
Michael Homer

3
Tôi thực sự nghĩ rằng câu hỏi là tốt bây giờ - tôi có một số lượng lớn các quy trình phụ. Tôi cần phải thu thập tất cả các mã thoát. Đó là tất cả.
Alexander Mills

Câu trả lời:


6

Câu trả lời của Alexander Mills sử dụng tay cầmJobs đã cho tôi một điểm khởi đầu tuyệt vời, nhưng cũng cho tôi lỗi này

cảnh báo: run_pending_traps: giá trị xấu trong bẫy_list [17]: 0x461010

Đó có thể là một vấn đề tình trạng chủng tộc bash

Thay vào đó tôi chỉ lưu trữ pid của từng đứa trẻ và chờ đợi và nhận mã thoát riêng cho từng đứa trẻ. Tôi tìm thấy trình dọn dẹp này về mặt các quy trình con sinh ra các quy trình con trong các chức năng và tránh nguy cơ chờ đợi một quy trình cha mẹ, nơi tôi có nghĩa là chờ đợi con. Nó rõ ràng hơn những gì xảy ra bởi vì nó không sử dụng bẫy.

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

tuyệt vời, tôi nghĩ rằng for job in "${childen[@]}"; donên được for job in "${1}"; domặc dù, cho rõ ràng
Alexander Mills

mối quan tâm duy nhất tôi có với kịch bản này, là nếu children_pids+=("$!")thực sự bắt được pid mong muốn cho shell phụ.
Alexander Mills

1
Tôi đã thử nghiệm với "$ {1}" và nó không hoạt động. Tôi đang truyền một mảng cho hàm và dường như cần chú ý đặc biệt đến bash. $! là mấu chốt của công việc được sinh ra lần cuối, xem tldp.org/LDP/abs/html/i INTERNalvariables.html Nó dường như hoạt động chính xác trong các thử nghiệm của tôi và hiện tôi đang sử dụng trong tập lệnh un_dID cache_dirs và nó dường như làm được công việc của nó Tôi đang sử dụng bash 4.4.12.
arberg

vâng, có vẻ như bạn đúng
Alexander Mills

20

Sử dụng waitvới một PID, sẽ:

Chờ cho đến khi quá trình con theo quy định của mỗi quá trình ID pid hoặc đặc tả công việc JOBSPEC thoát và trả lại trạng thái thoát của lệnh cuối cùng chờ đợi.

Bạn sẽ cần lưu PID của từng quy trình khi bạn thực hiện:

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

Bạn cũng có thể kích hoạt kiểm soát công việc trong tập lệnh với set -mvà sử dụng một %njobspec, nhưng bạn gần như chắc chắn không muốn - kiểm soát công việc có rất nhiều tác dụng phụ khác .

waitsẽ trả về mã giống như quá trình kết thúc. Bạn có thể sử dụng wait $Xtại bất kỳ điểm nào (hợp lý) sau này để truy cập mã cuối cùng $?hoặc đơn giản là sử dụng nó là true / false:

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

wait sẽ tạm dừng cho đến khi lệnh hoàn thành nếu nó chưa có.

Nếu bạn muốn tránh trì hoãn như vậy, bạn có thể thiết lập một traptrênSIGCHLD , đếm số chấm dứt, và xử lý tất cả các waits cùng một lúc khi họ đều đã hoàn thành. Bạn có thể có thể thoát khỏi việc sử dụng waitmột mình gần như mọi lúc.


1
ughh, xin lỗi, tôi cần chạy song song các mạng con này, tôi sẽ chỉ định điều đó trong câu hỏi ...
Alexander Mills

đừng bận tâm, có lẽ điều này hoạt động với thiết lập của tôi ... lệnh chờ trong đâu sẽ xuất hiện trong mã của bạn? Tôi không theo dõi
Alexander Mills

1
@AlexanderMills Họ đang chạy song song. Nếu bạn có số lượng biến của chúng, hãy sử dụng một mảng. (ví dụ như ở đây có thể là một bản sao).
Michael Homer

vâng cảm ơn tôi sẽ kiểm tra xem, nếu lệnh chờ liên quan đến câu trả lời của bạn, thì vui lòng thêm nó
Alexander Mills

Bạn chạy wait $Xở bất kỳ điểm nào (hợp lý) sau này.
Michael Homer

5

Nếu bạn có một cách tốt để xác định các lệnh, bạn có thể in mã thoát của chúng sang tệp tmp và sau đó truy cập vào tệp cụ thể mà bạn quan tâm:

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

Đừng quên xóa các tập tin tmp.


4

Sử dụng a compound command- đặt câu lệnh trong ngoặc đơn:

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

sẽ cho đầu ra

x
X: 0
TRUE: 0
FALSE: 1

Một cách thực sự khác nhau để chạy song song một số lệnh là sử dụng GNU Parallel . Tạo một danh sách các lệnh để chạy và đặt chúng vào tệp list:

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

Chạy song song tất cả các lệnh và thu thập mã thoát trong tệp job.log:

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

và đầu ra là:

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

ok cảm ơn, có cách nào để khái quát điều này? Tôi không có 3 quy trình phụ, tôi có quy trình phụ Z.
Alexander Mills

Tôi đã cập nhật câu hỏi ban đầu để phản ánh rằng tôi đang tìm kiếm một giải pháp chung, cảm ơn
Alexander Mills

Một cách để tạo ra nó có thể là sử dụng cấu trúc lặp?
Alexander Mills

Vòng lặp? Bạn có một danh sách cố định các lệnh hoặc được kiểm soát bởi người dùng? Tôi không chắc tôi hiểu những gì bạn đang cố gắng làm nhưng có lẽ PIPESTATUSđó là điều bạn nên kiểm tra. Điều này seq 10 | gzip -c > seq.gz ; echo ${PIPESTATUS[@]}trả về 0 0(mã thoát từ lệnh đầu tiên và cuối cùng).
hschou

Vâng về cơ bản được kiểm soát bởi người dùng
Alexander Mills

2

đây là kịch bản chung mà bạn đang tìm kiếm. Nhược điểm duy nhất là các lệnh của bạn nằm trong dấu ngoặc kép, có nghĩa là tô sáng cú pháp thông qua IDE của bạn sẽ không thực sự hoạt động. Mặt khác, tôi đã thử một vài câu trả lời khác và đây là câu trả lời hay nhất. Câu trả lời này kết hợp ý tưởng sử dụng wait <pid>được đưa ra bởi @Michael nhưng tiến thêm một bước bằng cách sử dụng traplệnh có vẻ hoạt động tốt nhất.

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

cảm ơn @michael homer đã giúp tôi đi đúng hướng, nhưng sử dụng traplệnh là cách tiếp cận tốt nhất AFAICT.


1
Bạn cũng có thể sử dụng bẫy SIGCHLD để xử lý trẻ em khi chúng thoát ra, chẳng hạn như in ra trạng thái tại thời điểm đó. Hoặc cập nhật bộ đếm tiến trình: khai báo một hàm sau đó sử dụng "bẫy function_name CHLD" mặc dù điều đó cũng có thể yêu cầu một tùy chọn được bật trong một vỏ không tương tác, chẳng hạn như "set -m"
Chunko

1
Ngoài ra "chờ -n" sẽ đợi bất kỳ đứa trẻ nào và sau đó trả lại trạng thái thoát của đứa trẻ đó trong $? Biến đổi. Vì vậy, bạn có thể in tiến trình khi mỗi người thoát. Tuy nhiên, lưu ý rằng trừ khi bạn sử dụng bẫy CHLD, bạn có thể bỏ lỡ một số trẻ thoát ra theo cách đó.
Chunko

@Chunko cảm ơn! đó là thông tin tốt, bạn có thể cập nhật câu trả lời với thứ gì đó bạn nghĩ là tốt nhất không?
Alexander Mills

cảm ơn @Chunko, bẫy hoạt động tốt hơn, bạn nói đúng. Với sự chờ đợi <pid>, tôi đã thất bại.
Alexander Mills

Bạn có thể giải thích làm thế nào và tại sao bạn tin rằng phiên bản với cái bẫy tốt hơn cái không có nó không? (Tôi tin rằng nó không tốt hơn, và do đó nó tồi tệ hơn, bởi vì nó phức tạp hơn không có lợi ích.)
Scott

1

Một biến thể khác của câu trả lời của @rolf:

Một cách khác để lưu trạng thái thoát sẽ là một cái gì đó như

mkdir /tmp/status_dir

và sau đó có từng kịch bản

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

Điều này cung cấp cho bạn một tên duy nhất cho mỗi tệp trạng thái bao gồm tên của tập lệnh đã tạo nó và id tiến trình của nó (trong trường hợp có nhiều phiên bản của cùng một tập lệnh đang chạy) mà bạn có thể lưu để tham khảo sau và đặt tất cả chúng vào cùng một vị trí để bạn có thể xóa toàn bộ thư mục con khi bạn hoàn tất.

Bạn thậm chí có thể lưu nhiều trạng thái từ mỗi tập lệnh bằng cách thực hiện một số thứ như

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

tạo tập tin như trước đây, nhưng thêm một chuỗi ngẫu nhiên duy nhất vào nó.

Hoặc, bạn chỉ có thể nối thêm thông tin trạng thái vào cùng một tệp.


1

script3sẽ chỉ được thực hiện nếu script1script2thành công script1script2sẽ được thực hiện song song:

./script1 &
process1=$!

./script2 &
process2=$!

wait $process1
rc1=$?

wait $process2
rc2=$?

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

AFAICT, đây không gì khác hơn là một bản tóm tắt lại câu trả lời của Michael Homer .
Scott
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.