ฉันจะจับรหัสทางออก / จัดการผิดได้อย่างถูกต้องเมื่อใช้การทดแทนกระบวนการได้อย่างไร


13

ฉันมีสคริปต์ที่แยกวิเคราะห์ชื่อไฟล์ลงในอาร์เรย์โดยใช้วิธีการต่อไปนี้ที่นำมาจากQ&A บน SO :

unset ARGS
ARGID="1"
while IFS= read -r -d $'\0' FILE; do
    ARGS[ARGID++]="$FILE"
done < <(find "$@" -type f -name '*.txt' -print0)

วิธีนี้ใช้งานได้ดีและจัดการรูปแบบชื่อไฟล์ทุกประเภทได้อย่างสมบูรณ์แบบ อย่างไรก็ตามบางครั้งฉันจะส่งไฟล์ที่ไม่มีอยู่ไปยังสคริปต์เช่น:

$ findscript.sh existingfolder nonexistingfolder
find: `nonexistingfile': No such file or directory
...

ภายใต้สถานการณ์ปกติฉันจะให้สคริปต์จับรหัสออกด้วยสิ่งที่ชอบRET=$?และใช้เพื่อตัดสินใจว่าจะดำเนินการอย่างไร ดูเหมือนจะไม่ทำงานกับการทดแทนกระบวนการด้านบน

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

คำตอบ:


5

คุณสามารถรับผลตอบแทนจากกระบวนการ subshelled ใด ๆ ได้ง่ายๆโดยการสะท้อนการส่งคืนผ่าน stdout เช่นเดียวกับการทดแทนกระบวนการ:

while IFS= read -r -d $'\0' FILE || 
    ! return=$FILE
do    ARGS[ARGID++]="$FILE"
done < <(find . -type f -print0; printf "$?")

ถ้าฉันรันสิ่งนั้นบรรทัดสุดท้ายสุด - (หรือ\0ส่วนที่คั่นด้วยแล้วแต่กรณี)จะเป็นfindสถานะการส่งคืน readกำลังจะส่งคืน 1 เมื่อได้รับ EOF ดังนั้นเวลาเดียวเท่านั้นที่$returnกำหนดไว้$FILEสำหรับข้อมูลสุดท้ายที่อ่าน

ฉันใช้printfเพื่อป้องกันการเพิ่ม\newline พิเศษ- นี่เป็นสิ่งสำคัญเพราะแม้แต่การreadแสดงเป็นประจำ - หนึ่งในสิ่งที่คุณไม่กำหนดใน\0NULs - จะคืนค่าอื่นที่ไม่ใช่ 0 ในกรณีที่ข้อมูลที่เพิ่งอ่านไม่สิ้นสุด\newline ดังนั้นหากบรรทัดสุดท้ายของคุณไม่ได้ลงท้ายด้วย\newline ค่าสุดท้ายในตัวแปร read in ของคุณจะเป็นผลตอบแทนของคุณ

ใช้คำสั่งด้านบนแล้ว:

echo "$return"

เอาท์พุท

0

และถ้าฉันเปลี่ยนส่วนทดแทนกระบวนการ ...

...
done < <(! find . -type f -print0; printf "$?")
echo "$return"

เอาท์พุท

1

การสาธิตที่ง่ายขึ้น:

printf \\n%s list of lines printed to pipe |
while read v || ! echo "$v"
do :; done

เอาท์พุท

pipe

และในความเป็นจริงตราบใดที่ผลตอบแทนที่คุณต้องการคือสิ่งสุดท้ายที่คุณเขียนถึง stdout จากภายในการทดแทนกระบวนการ - หรือกระบวนการ subshelled ใด ๆ ที่คุณอ่านด้วยวิธีนี้ - จากนั้น$FILEจะเป็นสถานะการส่งคืนที่คุณต้องการเสมอ ผ่าน และ|| ! return=...ส่วนที่ไม่จำเป็นอย่างเคร่งครัด - มันถูกใช้เพื่อแสดงแนวคิดเท่านั้น


5

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

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

 < <(find …; echo $? >find.status.tmp; mv find.status.tmp find.status)
while ! [ -e find.status ]; do sleep 1; done
find_status=$(cat find.status; rm find.status)

อีกวิธีหนึ่งคือการใช้ไพพ์ที่มีชื่อและกระบวนการพื้นหลัง (ซึ่งคุณสามารถทำได้wait)

mkfifo find_pipe
find  >find_pipe &
find_pid=$!
 <find_pipe
wait $find_pid
find_status=$?

หากวิธีการใดไม่เหมาะสมฉันคิดว่าคุณจะต้องเรียนภาษาที่มีความสามารถมากกว่าเช่น Perl, Python หรือ Ruby


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

2

ใช้ตัวประมวลผลร่วม การใช้coprocbuiltin คุณสามารถเริ่มต้นกระบวนการย่อยอ่านเอาต์พุตและตรวจสอบสถานะการออก:

coproc LS { ls existingdir; }
LS_PID_=$LS_PID
while IFS= read i; do echo "$i"; done <&"$LS"
wait "$LS_PID_"; echo $?

หากไดเรกทอรีไม่มีอยู่waitจะออกด้วยรหัสสถานะที่ไม่ใช่ศูนย์

ขณะนี้จำเป็นต้องคัดลอก PID ไปยังตัวแปรอื่นเนื่องจาก$LS_PIDจะไม่ถูกตั้งค่าก่อนที่จะwaitถูกเรียก ดูที่ตัวแปร Bash unsets * _PID ก่อนที่ฉันจะรอรายละเอียดcoproc


1
ฉันอยากรู้ว่าเมื่อใดจะใช้ <& "$ LS" เทียบกับอ่าน -u $ LS - ขอบคุณ
Brian Chrisman

1
@BrianChrisman ในกรณีนี้อาจไม่เคย read -uควรทำงานเช่นกัน ตัวอย่างตั้งใจให้เป็นแบบทั่วไปและแสดงว่าเอาต์พุตของตัวประมวลผลร่วมสามารถถูกไพพ์ลงในคำสั่งอื่นได้อย่างไร
Feuermurmel

1

วิธีการหนึ่งคือ:

status=0
token="WzNZY3CjqF3qkasn"    # some random string
while read line; do
    if [[ "$line" =~ $token:([[:digit:]]+) ]]; then
        status="${BASH_REMATCH[1]}"
    else
        echo "$line"
    fi
done < <(command; echo "$token:$?")
echo "Return code: $status"

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

อาจไม่ใช่วิธีที่ดีที่สุดในการเขียนโปรแกรมทั่วไป แต่อาจเป็นวิธีที่เจ็บปวดน้อยที่สุดในการจัดการในการทุบตี

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