รวบรวมรหัสทางออกของกระบวนการพื้นหลังแบบขนาน (เชลล์ย่อย)


18

สมมติว่าเรามีสคริปต์ทุบตีดังนี้:

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

มีวิธีในการรวบรวมรหัสทางออกของกระบวนการย่อย / กระบวนการย่อยหรือไม่? กำลังมองหาวิธีการทำสิ่งนี้และหาอะไรไม่เจอ ฉันต้องเรียกใช้ subshells เหล่านี้ในแบบคู่ขนานมิฉะนั้นจะทำได้ง่ายกว่านี้

ฉันกำลังมองหาโซลูชันทั่วไป (ฉันมีกระบวนการย่อยจำนวนไม่ทราบ / แบบไดนามิกเพื่อให้ทำงานแบบขนาน)


1
ฉันจะแนะนำให้คุณเข้าใจว่าคุณต้องการอะไรจากนั้นจึงถามคำถามใหม่พยายามอธิบายพฤติกรรมที่คุณต้องการอย่างชัดเจน
Michael Homer

3
ฉันคิดว่าจริง ๆ แล้วคำถามตอนนี้ดีมาก - ฉันมีกระบวนการย่อยจำนวนมาก ฉันต้องรวบรวมรหัสทางออกทั้งหมด นั่นคือทั้งหมดที่
Alexander Mills

คำตอบ:


6

คำตอบของ Alexander Mills ซึ่งใช้ handleJobs เป็นจุดเริ่มต้นที่ดี แต่ก็ทำให้ฉันมีข้อผิดพลาดเช่นกัน

คำเตือน: run_pending_traps: ค่าไม่ดีใน Trap_list [17]: 0x461010

ซึ่งอาจเป็นปัญหาสภาพการแข่งขันทุบตี

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

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

เจ๋งฉันคิดว่าน่าfor job in "${childen[@]}"; doจะเป็นfor job in "${1}"; doเช่นนั้นเพื่อความชัดเจน
Alexander Mills

ข้อกังวลเดียวที่ฉันมีกับสคริปต์นี้คือถ้าchildren_pids+=("$!")เป็นจริงที่จะจับ pid ที่ต้องการสำหรับเชลล์ย่อย
Alexander Mills

1
ฉันทดสอบด้วย "$ {1}" และใช้งานไม่ได้ ฉันส่งอาร์เรย์ไปยังฟังก์ชั่นและดูเหมือนว่าต้องการความสนใจเป็นพิเศษในการทุบตี $! เป็น pid ของงานวางไข่ครั้งล่าสุดดูtldp.org/LDP/abs/html/internalvariables.htmlดูเหมือนว่าจะทำงานได้อย่างถูกต้องในการทดสอบของฉันและตอนนี้ฉันกำลังใช้สคริปต์สคริปต์ un_did cache_dirs และดูเหมือนว่าจะทำ งานของมัน ฉันใช้ทุบตี 4.4.12
arberg

อ๋อดีดูเหมือนว่าคุณถูกต้อง
อเล็กซานเด Mills

20

ใช้waitกับ PID ซึ่งจะ:

รอจนกว่ากระบวนการลูกที่ระบุโดยแต่ละ ID กระบวนการpidหรือjobpecข้อมูลจำเพาะงานออกและส่งกลับสถานะการออกของคำสั่งสุดท้ายที่รอ

คุณจะต้องบันทึก PID ของแต่ละขั้นตอนขณะดำเนินการ:

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

นอกจากนี้คุณยังสามารถเปิดใช้งานการควบคุมงานในสคริปต์ด้วยset -mและใช้%njobspec แต่คุณแทบไม่ต้องการ - การควบคุมงานมีผลข้างเคียงมากมาย

waitจะส่งคืนรหัสเดียวกันกับกระบวนการที่เสร็จสิ้น คุณสามารถใช้wait $Xที่ใดก็ได้ (สมเหตุสมผล) ในภายหลังเพื่อเข้าถึงรหัสสุดท้าย$?หรือเพียงแค่ใช้มันเป็นจริง / เท็จ:

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

wait จะหยุดชั่วคราวจนกว่าคำสั่งจะเสร็จสมบูรณ์หากยังไม่ได้ดำเนินการ

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


1
อุ๊บขอโทษผมจำเป็นต้องเรียกใช้ subshells เหล่านี้ในแบบคู่ขนานผมจะระบุว่าในคำถาม ...
อเล็กซานเดมิลส์

ไม่เป็นไรอาจเป็นไปได้กับการตั้งค่าของฉัน ... คำสั่ง wait จะปรากฏในรหัสของคุณที่ไหน ฉันไม่ได้ติดตาม
Alexander Mills

1
@AlexanderMills พวกเขากำลังทำงานแบบขนาน หากคุณมีหมายเลขตัวแปรให้ใช้อาร์เรย์ (เช่นที่นี่ซึ่งอาจซ้ำกัน)
Michael Homer

ใช่ขอบคุณฉันจะตรวจสอบว่าถ้าคำสั่งรอเกี่ยวข้องกับคำตอบของคุณโปรดเพิ่มมัน
อเล็กซานเดอร์มิลส์

คุณทำงานwait $Xที่จุดใด ๆ (สมเหตุสมผล) ในภายหลัง
Michael Homer

5

หากคุณมีวิธีที่ดีในการระบุคำสั่งคุณสามารถพิมพ์รหัสออกของพวกเขาไปยังไฟล์ tmp จากนั้นเข้าถึงไฟล์เฉพาะที่คุณสนใจ:

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


4

ใช้compound command- ใส่คำสั่งในวงเล็บ:

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

จะให้ผลลัพธ์

x
X: 0
TRUE: 0
FALSE: 1

วิธีที่แตกต่างกันจริงๆเรียกใช้คำสั่งหลายขนานโดยใช้GNU ขนาน ทำรายการคำสั่งเพื่อเรียกใช้และวางไว้ในไฟล์list:

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

รันคำสั่งทั้งหมดแบบขนานและรวบรวมรหัสทางออกในไฟล์job.log:

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

และผลลัพธ์คือ:

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

โอเคขอบคุณมีวิธีสร้างสิ่งนี้หรือไม่? ฉันไม่ได้มีเพียงแค่ 3 กระบวนการย่อยฉันมีกระบวนการย่อย Z
Alexander Mills

ฉันได้อัปเดตคำถามเดิมเพื่อแสดงว่าฉันกำลังมองหาวิธีแก้ปัญหาทั่วไปขอบคุณ
Alexander Mills

วิธีหนึ่งในการสร้างมันอาจจะเป็นการใช้โครงสร้างลูป?
Alexander Mills

วนรอบ? คุณมีรายการคำสั่งคงที่หรือถูกควบคุมโดยผู้ใช้หรือไม่? ฉันไม่แน่ใจว่าฉันเข้าใจสิ่งที่คุณพยายามทำ แต่อาจPIPESTATUSเป็นสิ่งที่คุณควรตรวจสอบ seq 10 | gzip -c > seq.gz ; echo ${PIPESTATUS[@]}ผลตอบแทนนี้0 0(รหัสทางออกจากคำสั่งแรกและสุดท้าย)
hschou

ใช่ควบคุมโดยผู้ใช้เป็นหลัก
Alexander Mills

2

นี่เป็นสคริปต์ทั่วไปที่คุณต้องการ ข้อเสียเพียงอย่างเดียวคือคำสั่งของคุณอยู่ในเครื่องหมายคำพูดซึ่งหมายถึงการเน้นไวยากรณ์ผ่าน IDE ของคุณจะไม่ทำงาน มิฉะนั้นฉันได้ลองคำตอบอื่น ๆ สองสามข้อและนี่คือคำตอบที่ดีที่สุด คำตอบนี้รวมแนวคิดของการใช้ที่wait <pid>ได้รับจาก @Michael แต่ไปอีกขั้นหนึ่งโดยใช้trapคำสั่งที่ดูเหมือนจะทำงานได้ดีที่สุด

#!/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 ที่ทำให้ฉันไปถูกทาง แต่การใช้trapคำสั่งเป็นวิธีที่ดีที่สุด


1
คุณยังสามารถใช้ SIGCHLD trap เพื่อประมวลผลลูก ๆ เมื่อพวกเขาออกจากเช่นการพิมพ์สถานะในเวลานั้น หรืออัปเดตตัวนับความคืบหน้า: ประกาศฟังก์ชั่นจากนั้นใช้ "trap function_name CHLD" ซึ่งอาจต้องเปิดใช้ตัวเลือกในเชลล์ที่ไม่มีการโต้ตอบเช่นอาจเป็น "set -m"
Chunko

1
นอกจากนี้ "wait -n" จะรอเด็ก ๆ จากนั้นส่งคืนสถานะการออกของเด็กคนนั้นใน $? ตัวแปร. เพื่อให้คุณสามารถพิมพ์ความคืบหน้าขณะที่แต่ละคนออกจาก อย่างไรก็ตามโปรดทราบว่าหากคุณไม่ใช้กับดัก CHLD คุณอาจพลาดเด็กบางคนที่ออกไปทางนั้น
Chunko

@ Chunko ขอบคุณ! นั่นเป็นข้อมูลที่ดีบางทีคุณอาจปรับปรุงคำตอบด้วยบางสิ่งที่คุณคิดว่าดีที่สุด?
Alexander Mills

ขอบคุณ @Chunko กับดักทำงานได้ดีขึ้นคุณพูดถูก ด้วยการรอ <pid> ฉันผิดพลาด
Alexander Mills

คุณสามารถอธิบายได้ว่าทำไมและทำไมคุณถึงเชื่อว่าเวอร์ชั่นที่มีกับดักนั้นดีกว่าอันที่ไม่มีมัน? (ฉันเชื่อว่ามันไม่ดีขึ้นและดังนั้นจึงเป็นเรื่องที่เลวร้ายยิ่งขึ้นเพราะมันซับซ้อนมากขึ้นโดยไม่ได้รับประโยชน์)
Scott

1

อีกรูปแบบของคำตอบของ @rolf:

อีกวิธีในการบันทึกสถานะการออกจะเป็นสิ่งที่ต้องการ

mkdir /tmp/status_dir

จากนั้นให้แต่ละสคริปต์

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

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

คุณสามารถบันทึกมากกว่าหนึ่งสถานะจากแต่ละสคริปต์ด้วยการทำสิ่งที่ชอบ

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

ซึ่งสร้างไฟล์เหมือน แต่ก่อนเพิ่มสตริงแบบสุ่มที่ไม่ซ้ำกัน

หรือคุณสามารถเพิ่มข้อมูลสถานะเพิ่มเติมลงในไฟล์เดียวกันได้


1

script3จะถูกดำเนินการหากscript1และscript2ประสบความสำเร็จและscript1และscript2จะถูกดำเนินการแบบขนาน

./script1 &
process1=$!

./script2 &
process2=$!

wait $process1
rc1=$?

wait $process2
rc2=$?

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

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