ฉันมีสองกระบวนการfoo
และbar
เชื่อมต่อกับท่อ:
$ foo | bar
bar
ออกจาก 0 เสมอ foo
ฉันสนใจในรหัสทางออกของ มีวิธีใดบ้างที่จะไปถึงที่นั่น?
ฉันมีสองกระบวนการfoo
และbar
เชื่อมต่อกับท่อ:
$ foo | bar
bar
ออกจาก 0 เสมอ foo
ฉันสนใจในรหัสทางออกของ มีวิธีใดบ้างที่จะไปถึงที่นั่น?
คำตอบ:
หากคุณกำลังใช้bash
คุณสามารถใช้PIPESTATUS
ตัวแปรอาร์เรย์เพื่อรับสถานะการออกของแต่ละองค์ประกอบของไปป์ไลน์
$ false | true
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
1 0
หากคุณกำลังใช้zsh
งานอาร์เรย์เหล่านั้นจะถูกเรียกpipestatus
(case case!) และดัชนี array เริ่มต้นที่หนึ่ง:
$ false | true
$ echo "${pipestatus[1]} ${pipestatus[2]}"
1 0
หากต้องการรวมเข้าด้วยกันในฟังก์ชั่นในลักษณะที่ไม่สูญเสียค่า:
$ false | true
$ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$?
$ echo $retval_bash $retval_zsh $retval_final
1 0
เรียกใช้ข้างต้นในbash
หรือzsh
และคุณจะได้ผลลัพธ์เดียวกัน มีเพียงหนึ่งรายการเท่านั้นretval_bash
และretval_zsh
จะถูกตั้งค่า อีกอันจะว่างเปล่า สิ่งนี้จะทำให้ฟังก์ชั่นลงท้ายด้วยreturn $retval_bash $retval_zsh
(หมายเหตุการขาดเครื่องหมายคำพูด!)
pipestatus
ใน zsh น่าเสียดายที่กระสุนอื่นไม่มีคุณสมบัตินี้
echo "$pipestatus[1]" "$pipestatus[2]"
ตั้งใจที่ดัชนี 1 ดังนั้นจึงเป็น
if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
การทำเช่นนี้มี 3 วิธี:
วิธีแรกคือการตั้งค่าpipefail
ตัวเลือก ( ksh
, zsh
หรือbash
) นี่คือวิธีที่ง่ายที่สุดและสิ่งที่มันทำคือการตั้งค่าสถานะ$?
การออกเป็นรหัสทางออกของโปรแกรมสุดท้ายเพื่อออกไม่ใช่ศูนย์ (หรือศูนย์ถ้าออกทั้งหมดประสบความสำเร็จ)
$ false | true; echo $?
0
$ set -o pipefail
$ false | true; echo $?
1
Bash ยังมีตัวแปรอาเรย์ที่เรียกว่า$PIPESTATUS
( $pipestatus
ในzsh
) ซึ่งมีสถานะทางออกของโปรแกรมทั้งหมดในขั้นตอนสุดท้าย
$ true | true; echo "${PIPESTATUS[@]}"
0 0
$ false | true; echo "${PIPESTATUS[@]}"
1 0
$ false | true; echo "${PIPESTATUS[0]}"
1
$ true | false; echo "${PIPESTATUS[@]}"
0 1
คุณสามารถใช้ตัวอย่างคำสั่งที่ 3 เพื่อรับค่าเฉพาะในไปป์ไลน์ที่คุณต้องการ
นี่เป็นวิธีแก้ปัญหาที่ไม่สะดวกที่สุด เรียกใช้แต่ละคำสั่งแยกจากกันและจับภาพสถานะ
$ OUTPUT="$(echo foo)"
$ STATUS_ECHO="$?"
$ printf '%s' "$OUTPUT" | grep -iq "bar"
$ STATUS_GREP="$?"
$ echo "$STATUS_ECHO $STATUS_GREP"
0 1
ksh
แต่จากคร่าว ๆ มัน manpage มันไม่สนับสนุน$PIPESTATUS
หรืออะไรที่คล้ายกัน มันสนับสนุนpipefail
ตัวเลือกว่า
LOG=$(failed_command | successful_command)
วิธีนี้ใช้งานได้โดยไม่ต้องใช้คุณสมบัติเฉพาะทุบตีหรือไฟล์ชั่วคราว โบนัส: ในที่สุดสถานะการออกเป็นสถานะการออกจริงและไม่ใช่สตริงในไฟล์
สถานการณ์:
someprog | filter
คุณต้องการสถานะออกจากและเอาท์พุทจากsomeprog
filter
นี่คือทางออกของฉัน:
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
ผลลัพธ์ของการสร้างนี้คือ stdout จากfilter
เป็น stdout ของการสร้างและสถานะการออกจากsomeprog
เป็นสถานะการออกของการก่อสร้าง
โครงสร้างนี้ยังทำงานร่วมกับคำสั่งง่ายๆการจัดกลุ่ม{...}
แทน (...)
subshells subshells มีความหมายบางอย่างในหมู่คนอื่น ๆ ค่าใช้จ่ายในการปฏิบัติงานซึ่งเราไม่ต้องการที่นี่ อ่านคู่มือทุบตีละเอียดสำหรับรายละเอียดเพิ่มเติม: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html
{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1
น่าเสียดายที่ไวยากรณ์ของ bash นั้นต้องการช่องว่างและเครื่องหมายอัฒภาคสำหรับการจัดฟันแบบหยิกเพื่อให้การสร้างมีขนาดกว้างขวางขึ้น
สำหรับส่วนที่เหลือของข้อความนี้ฉันจะใช้ตัวแปรย่อย
ตัวอย่างsomeprog
และfilter
:
someprog() {
echo "line1"
echo "line2"
echo "line3"
return 42
}
filter() {
while read line; do
echo "filtered $line"
done
}
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
echo $?
ตัวอย่างผลลัพธ์:
filtered line1
filtered line2
filtered line3
42
หมายเหตุ: กระบวนการย่อยสืบทอดตัวอธิบายไฟล์ที่เปิดอยู่จากพาเรนต์ นั่นหมายความว่าsomeprog
จะสืบทอด open file descriptor 3 และ 4 ถ้าsomeprog
เขียนไปยัง file descriptor 3 ดังนั้นนั่นจะกลายเป็นสถานะการออก สถานะการออกจริงจะถูกละเว้นเพราะread
จะอ่านเพียงครั้งเดียว
หากคุณกังวลว่าคุณsomeprog
อาจจะเขียนไปยังแฟ้มบ่ง 3 หรือ 4 someprog
แล้วมันเป็นที่ดีที่สุดที่จะปิดอธิบายไฟล์ก่อนที่จะเรียก
(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
exec 3>&- 4>&-
ก่อนที่จะsomeprog
ปิดอธิบายไฟล์ก่อนที่จะดำเนินsomeprog
ดังนั้นสำหรับsomeprog
ผู้ที่อธิบายไฟล์ก็ไม่อยู่
มันสามารถเขียนได้เช่นนี้ someprog 3>&- 4>&-
คำอธิบายทีละขั้นตอนของการสร้าง:
( ( ( ( someprog; #part6
echo $? >&3 #part5
) | filter >&4 #part4
) 3>&1 #part3
) | (read xs; exit $xs) #part2
) 4>&1 #part1
จากล่างขึ้นบน:
#part3
) และขวา ( #part2
) จะถูกดำเนินการ exit $xs
เป็นคำสั่งสุดท้ายของไปป์และนั่นหมายความว่าสตริงจาก stdin จะเป็นสถานะทางออกของการสร้างทั้งหมด#part2
และจะเป็นสถานะทางออกของการสร้างทั้งหมด#part5
และ#part6
) และขวา ( filter >&4
) จะถูกดำเนินการ เอาต์พุตของfilter
ถูกเปลี่ยนทิศทางไปยัง file descriptor 4 ใน#part1
ไฟล์ descriptor 4 ถูกเปลี่ยนทิศทางไปที่ stdout ซึ่งหมายความว่าผลลัพธ์ของfilter
คือ stdout ของการสร้างทั้งหมด#part6
จะมีการพิมพ์ที่จะยื่นบ่ง 3. ใน#part3
ไฟล์อธิบาย 3 #part2
ถูกเปลี่ยนเส้นทางไปยัง ซึ่งหมายความว่าสถานะการออกจาก#part6
จะเป็นสถานะการออกสุดท้ายสำหรับการก่อสร้างทั้งหมดsomeprog
ถูกประหารชีวิต #part5
ออกจากสถานะที่มีการดำเนินการใน stdout จะนำโดยท่อในและส่งต่อไปยัง#part4
filter
เอาต์พุตจากfilter
จะถึง stdout ตามที่อธิบายไว้ใน#part4
(read; exit $REPLY)
(exec 3>&- 4>&-; someprog)
someprog 3>&- 4>&-
ช่วยลดความยุ่งยากในการ
{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
แม้ว่าจะไม่ใช่สิ่งที่คุณถามคุณสามารถใช้
#!/bin/bash -o pipefail
เพื่อให้ไพพ์ของคุณส่งกลับค่าไม่ใช่ศูนย์สุดท้าย
อาจเป็นรหัสน้อย
แก้ไข: ตัวอย่าง
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
0
[root@localhost ~]# set -o pipefail
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
1
set -o pipefail
bash foo.sh
ภายในสคริปต์ที่ควรจะมีประสิทธิภาพมากขึ้นเช่นในกรณีที่มีคนรันสคริปต์ผ่าน
-o pipefail
อยู่ใน POSIX
#!/bin/bash -o pipefail
ฉันมี ข้อผิดพลาดคือ:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
#!
บรรทัดที่เกินจากบรรทัดแรกและดังนั้นสิ่งนี้จะกลายเป็น/bin/bash
-o pipefail
/tmp/ff
แทนที่จะเป็นสิ่งที่จำเป็น/bin/bash
-o
pipefail
/tmp/ff
- getopt
(หรือคล้ายกัน) การแยกวิเคราะห์โดยใช้รายการoptarg
ซึ่งเป็นรายการถัดไปARGV
เป็นอาร์กิวเมนต์ ถึง-o
ดังนั้นจึงล้มเหลว หากคุณต้องการทำเสื้อคลุม (พูดbash-pf
เพียงแค่ทำexec /bin/bash -o pipefail "$@"
และวางไว้ใน#!
บรรทัดที่จะทำงานดูเพิ่มเติมที่: en.wikipedia.org/wiki/Shebang_%28Unix%29
สิ่งที่ฉันทำเมื่อเป็นไปได้คือการให้อาหารรหัสทางออกจากเข้าfoo
bar
ตัวอย่างเช่นหากฉันรู้ว่าfoo
ไม่เคยสร้างบรรทัดที่มีเพียงตัวเลขฉันก็สามารถวางรหัสทางออกได้
{ foo; echo "$?"; } | awk '!/[^0-9]/ {exit($0)} {…}'
หรือถ้าฉันรู้ว่าผลลัพธ์จากfoo
ไม่เคยมีบรรทัดที่มีเพียงแค่.
:
{ foo; echo .; echo "$?"; } | awk '/^\.$/ {getline; exit($0)} {…}'
สิ่งนี้สามารถทำได้หากมีวิธีการbar
ทำงานทั้งหมดยกเว้นบรรทัดสุดท้ายและส่งผ่านบรรทัดสุดท้ายเป็นรหัสออก
หากbar
เป็นไพพ์ไลน์ที่ซับซ้อนที่คุณไม่ต้องการเอาต์พุตคุณสามารถข้ามส่วนของมันได้โดยพิมพ์รหัสออกบนตัวอธิบายไฟล์อื่น
exit_codes=$({ { foo; echo foo:"$?" >&3; } |
{ bar >/dev/null; echo bar:"$?" >&3; }
} 3>&1)
หลังจากนี้$exit_codes
มักเป็นfoo:X bar:Y
ไปได้ แต่อาจเป็นไปได้bar:Y foo:X
ว่าbar
ก่อนที่จะอ่านอินพุตทั้งหมดหรือหากคุณโชคไม่ดี ฉันคิดว่าการเขียนไปยังไพพ์ไลน์สูงสุด 512 ไบต์เป็นอะตอมมิกในทุกยูนิเซฟดังนั้นfoo:$?
และbar:$?
ส่วนต่างๆจะไม่ถูกรวมกันตราบใดที่สตริงแท็กมีขนาดไม่เกิน 507 ไบต์
หากคุณต้องการจับเอาท์พุทจากbar
มันจะยาก คุณสามารถรวมเทคนิคต่าง ๆ ข้างต้นได้โดยจัดเรียงเอาต์พุตbar
ไม่ให้มีบรรทัดที่ดูเหมือนตัวบ่งชี้รหัสการออก แต่มันจะดูยุ่งเหยิง
output=$(echo;
{ { foo; echo foo:"$?" >&3; } |
{ bar | sed 's/^/^/'; echo bar:"$?" >&3; }
} 3>&1)
nl='
'
foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*}
bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*}
output=$(printf %s "$output" | sed -n 's/^\^//p')
และแน่นอนว่ามีตัวเลือกง่าย ๆ ในการใช้ไฟล์ชั่วคราวเพื่อเก็บสถานะ เรียบง่าย แต่ไม่ว่าง่ายในการผลิต:
/tmp
เป็นที่เดียวที่สคริปต์จะสามารถเขียนไฟล์ได้ ใช้mktemp
งานซึ่งไม่ใช่ POSIX แต่มีให้บริการใน unices ร้ายแรงทั้งหมดในปัจจุบันfoo_ret_file=$(mktemp -t)
{ foo; echo "$?" >"$foo_ret_file"; } | bar
bar_ret=$?
foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")
เริ่มต้นจากท่อ:
foo | bar | baz
นี่คือโซลูชันทั่วไปที่ใช้ POSIX เชลล์เท่านั้นและไม่มีไฟล์ชั่วคราว:
exec 4>&1
error_statuses="`((foo || echo "0:$?" >&3) |
(bar || echo "1:$?" >&3) |
(baz || echo "2:$?" >&3)) 3>&1 >&4`"
exec 4>&-
$error_statuses
มีรหัสสถานะของกระบวนการที่ล้มเหลวใด ๆ ตามลำดับแบบสุ่มพร้อมดัชนีเพื่อบอกคำสั่งที่ปล่อยแต่ละสถานะ
# if "bar" failed, output its status:
echo "$error_statuses" | grep '1:' | cut -d: -f2
# test if all commands succeeded:
test -z "$error_statuses"
# test if the last command succeeded:
! echo "$error_statuses" | grep '2:' >/dev/null
สังเกตคำพูด$error_statuses
ในการทดสอบของฉัน; หากไม่มีพวกเขาgrep
จะไม่สามารถแยกความแตกต่างได้เนื่องจากบรรทัดใหม่ถูกบังคับให้มีช่องว่าง
ดังนั้นฉันจึงอยากให้คำตอบเหมือน Lesmana แต่ฉันคิดว่าของฉันอาจจะง่ายกว่าและบริสุทธิ์กว่าเล็กน้อย - Bourne-shell solution:
# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.
ฉันคิดว่านี่เป็นคำอธิบายที่ดีที่สุดจากภายในออก - command1 จะดำเนินการและพิมพ์เอาต์พุตปกติบน stdout (file descriptor 1) จากนั้นเมื่อเสร็จแล้ว printf จะดำเนินการและพิมพ์รหัสทางออกของ command1 บน stdout แต่ stdout นั้นถูกเปลี่ยนเส้นทางไป ตัวอธิบายไฟล์ 3.
ในขณะที่ command1 กำลังทำงาน stdout ของมันจะถูกไพพ์ไปที่ command2 (เอาต์พุตของ printf ไม่เคยทำให้มันเป็น command2 เพราะเราส่งมันไปที่ file descriptor 3 แทนที่จะเป็น 1 ซึ่งเป็นสิ่งที่ pipe อ่าน จากนั้นเราเปลี่ยนเส้นทางเอาต์พุตของ command2 ไปยัง file descriptor 4 เพื่อให้มันยังคงอยู่จาก file descriptor 1 - เพราะเราต้องการ file descriptor 1 ฟรีอีกเล็กน้อยในภายหลังเพราะเราจะนำเอา printf บน file descriptor 3 1 - เพราะนั่นคือสิ่งที่การทดแทนคำสั่ง (backticks) จะจับภาพและนั่นคือสิ่งที่จะถูกวางลงในตัวแปร
บิตสุดท้ายของเวทมนตร์คือสิ่งแรกที่exec 4>&1
เราทำในฐานะคำสั่งแยกต่างหาก - มันเปิดไฟล์ descriptor 4 เป็นสำเนาของ stdout ของเปลือกนอก การทดแทนคำสั่งจะจับสิ่งที่เขียนบนมาตรฐานออกมาจากมุมมองของคำสั่งภายใน - แต่เนื่องจากเอาต์พุตของ command2 จะไปที่ไฟล์ descriptor 4 เท่าที่เกี่ยวข้องกับการทดแทนคำสั่งการแทนที่คำสั่งจะไม่จับ - อย่างไรก็ตาม เมื่อได้รับ "ออก" ของการทดแทนคำสั่งมันยังคงเป็นไปอย่างมีประสิทธิภาพเพื่ออธิบายไฟล์โดยรวมของสคริปต์ 1
( exec 4>&1
จะต้องมีคำสั่งแยกต่างหากเพราะเชลล์ทั่วไปจำนวนมากไม่ชอบเมื่อคุณพยายามเขียนไปยังไฟล์ descriptor ภายในการทดแทนคำสั่งที่เปิดในคำสั่ง "ภายนอก" ที่ใช้การทดแทนดังนั้นนี่คือ วิธีพกพาที่ง่ายที่สุดที่จะทำ)
คุณสามารถดูได้ในเชิงเทคนิคน้อยและขี้เล่นมากขึ้นราวกับว่าเอาต์พุตของคำสั่งกระโดดข้ามกัน: command1 ไพพ์ไปยัง command2 ดังนั้นเอาต์พุตของ printf ข้ามคำสั่ง 2 ดังนั้น command2 จึงไม่จับแล้ว เอาต์พุตของคำสั่ง 2 กระโดดข้ามไปและออกจากการทดแทนคำสั่งเช่นเดียวกับ printf ลงทันเวลาที่จะถูกดักจับโดยการทดแทนเพื่อให้มันจบลงในตัวแปรและเอาต์พุตของ command2 ดำเนินไปในลักษณะที่ถูกเขียนไปยังเอาต์พุตมาตรฐาน ในท่อปกติ
นอกจากนี้ตามที่ฉันเข้าใจ$?
จะยังคงมีโค้ดส่งคืนของคำสั่งที่สองในไพพ์เนื่องจากการกำหนดตัวแปรการแทนที่คำสั่งและคำสั่งผสมนั้นโปร่งใสทั้งหมดในโค้ดส่งคืนของคำสั่งภายในดังนั้นสถานะการส่งคืนของ command2 ควรแพร่กระจายออกไป - สิ่งนี้และไม่ต้องกำหนดฟังก์ชั่นเพิ่มเติมคือเหตุผลที่ฉันคิดว่านี่อาจเป็นทางออกที่ดีกว่าที่ lesmana เสนอ
ตามที่ระบุใน lesmana lesmana อาจเป็นไปได้ว่าคำสั่ง 1 จะจบลงด้วยการใช้ file descriptors 3 หรือ 4 เพื่อให้มีประสิทธิภาพมากขึ้นคุณจะต้อง:
exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-
โปรดทราบว่าฉันใช้คำสั่งผสมในตัวอย่างของฉัน แต่ใช้ subshells (การใช้( )
แทน{ }
จะใช้ได้แม้ว่าอาจจะมีประสิทธิภาพน้อยลง)
คำสั่งสืบทอดไฟล์ descriptor จากกระบวนการที่เรียกใช้ดังนั้นบรรทัดที่สองทั้งหมดจะสืบทอดไฟล์ descriptor สี่และคำสั่งผสมตามด้วย3>&1
จะสืบทอดสืบทอด descriptor ไฟล์สาม ดังนั้นการ4>&-
ทำให้แน่ใจว่าคำสั่งผสมภายในจะไม่สืบทอดตัวอธิบายไฟล์สี่และ3>&-
จะไม่สืบทอดตัวอธิบายไฟล์สามดังนั้น command1 จะได้รับ 'สภาพแวดล้อมที่สะอาดกว่า' และเป็นมาตรฐานมากขึ้น คุณสามารถขยับด้านใน4>&-
ข้างๆ3>&-
แต่ฉันคิดว่าทำไมไม่ จำกัด ขอบเขตเท่าที่จะทำได้
ฉันไม่แน่ใจว่าบ่อยครั้งที่สิ่งต่าง ๆ ใช้ file descriptor สามหรือสี่โดยตรง - ฉันคิดว่าโปรแกรมส่วนใหญ่ใช้ syscalls ที่ส่งคืนไฟล์ descriptor ที่ไม่ได้ใช้ที่ไม่ได้ใช้แล้ว แต่บางครั้งโค้ดเขียนไปยัง file descriptor 3 โดยตรงฉัน เดา (ฉันนึกภาพออกว่าโปรแกรมกำลังตรวจสอบ file descriptor เพื่อดูว่ามันเปิดอยู่หรือไม่และใช้มันถ้ามันเป็นหรือทำงานต่างไปตามนั้นถ้าไม่ใช่) ดังนั้นสิ่งหลังน่าจะดีที่สุดที่คุณควรคำนึงถึงและใช้สำหรับกรณีทั่วไป
-bash: 3: Bad file descriptor
ฉันได้รับ
หากคุณมีmoreutilsแพคเกจติดตั้งคุณสามารถใช้mispipeยูทิลิตี้ซึ่งไม่ว่าสิ่งที่คุณถาม
วิธีการแก้ปัญหาของ lesmana ด้านบนสามารถทำได้โดยไม่ต้องมีค่าใช้จ่ายในการเริ่มต้นกระบวนการย่อยแบบซ้อนโดยใช้{ .. }
แทน (โปรดจำไว้ว่าคำสั่งที่จัดกลุ่มรูปแบบนี้จะต้องจบด้วยเครื่องหมายอัฒภาคเสมอ) บางสิ่งเช่นนี้
{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1
ฉันได้ตรวจสอบโครงสร้างนี้ด้วยขีดกลางเวอร์ชัน 0.5.5 และ bash เวอร์ชัน 3.2.25 และ 4.2.42 ดังนั้นแม้ว่าบางเชลล์ไม่รองรับการ{ .. }
จัดกลุ่ม แต่ก็ยังเป็นไปตาม POSIX
set -o pipefail
ksh หรือwait
คำสั่งโรยจำนวนใด ๆ ก็ตาม ฉันคิดว่ามันอาจเป็นปัญหาในการแยกวิเคราะห์สำหรับ ksh อย่างน้อยที่สุดถ้าฉันใช้ subshells แล้วก็ใช้งานได้ดี แต่ถึงแม้จะมีการif
เลือกตัวแปร subshell สำหรับ ksh แต่ปล่อยให้คำสั่งผสมสำหรับผู้อื่นมันล้มเหลว .
สิ่งนี้สามารถเคลื่อนย้ายได้เช่นทำงานกับเชลล์ที่ทำงานร่วมกับ POSIX ใด ๆ ไม่ต้องการไดเรกทอรีปัจจุบันที่สามารถเขียนได้และอนุญาตให้สคริปต์หลาย ๆ
(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))
แก้ไข: นี่เป็นรุ่นที่ดีกว่าตามความคิดเห็นของ Gilles:
(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))
Edit2: และนี่คือตัวแปรที่เบากว่าเล็กน้อยตามความคิดเห็น dubiousjim:
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
นี้: @Johan: ฉันเห็นด้วยกับ Bash ง่ายกว่า แต่ในบางบริบทการรู้วิธีหลีกเลี่ยง Bash นั้นคุ้มค่า
การติดตามต่อไปนี้มีความหมายว่าเป็นส่วนเพิ่มเติมสำหรับคำตอบของ @Patrik ในกรณีที่คุณไม่สามารถใช้หนึ่งในวิธีแก้ปัญหาทั่วไป
คำตอบนี้ถือว่าต่อไปนี้:
$PIPESTATUS
หรือset -o pipefail
สมมติฐานเพิ่มเติม คุณสามารถกำจัดได้ทั้งหมด แต่สิ่งนี้ปิดบังสูตรมากเกินไปดังนั้นจึงไม่ครอบคลุมที่นี่:
- สิ่งที่คุณต้องการรู้คือคำสั่งทั้งหมดใน PIPE มีรหัสทางออก 0
- คุณไม่ต้องการข้อมูลแถบด้านข้างเพิ่มเติม
- เชลล์ของคุณรอให้คำสั่งไพพ์คืนกลับมา
ก่อนหน้า: foo | bar | baz
อย่างไรก็ตามสิ่งนี้จะส่งคืนรหัสออกของคำสั่งสุดท้ายเท่านั้น ( baz
)
ต้องการ: $?
ต้องไม่เป็น0
(จริง) หากคำสั่งใด ๆ ในไพพ์ล้มเหลว
หลังจาก:
TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"
{ foo || echo $? >&9; } |
{ bar || echo $? >&9; } |
{ baz || echo $? >&9; }
#wait
! read TMPRESULTS <&8
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"
# $? now is 0 only if all commands had exit code 0
อธิบาย:
mktemp
ถูกสร้างขึ้นด้วย โดยปกติจะสร้างไฟล์ในทันที/tmp
wait
เป็นสิ่งจำเป็นสำหรับksh
เพราะksh
อื่นไม่รอให้ท่อทุกคำสั่งที่จะเสร็จสิ้น อย่างไรก็ตามโปรดทราบว่ามีผลข้างเคียงที่ไม่พึงประสงค์หากมีงานเบื้องหลังบางอย่างดังนั้นฉันจึงแสดงความคิดเห็นโดยค่าเริ่มต้น หากการรอไม่เจ็บคุณสามารถแสดงความคิดเห็นได้read
กลับfalse
มาtrue
แสดงว่ามีข้อผิดพลาดสิ่งนี้สามารถใช้เป็นการแทนที่ปลั๊กอินสำหรับคำสั่งเดียวและต้องการเพียงต่อไปนี้:
/proc/fd/N
ข้อบกพร่อง:
สคริปต์นี้มีข้อบกพร่องในกรณีที่/tmp
พื้นที่ไม่เพียงพอ หากคุณต้องการป้องกันกรณีเทียมนี้ด้วยคุณสามารถทำได้ดังนี้อย่างไรก็ตามนี่มีข้อเสียที่จำนวน0
ในนั้น000
ขึ้นอยู่กับจำนวนคำสั่งในไพพ์ดังนั้นมันจึงซับซ้อนกว่าเล็กน้อย:
TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"
{ foo; printf "%1s" "$?" >&9; } |
{ bar; printf "%1s" "$?" >&9; } |
{ baz; printf "%1s" "$?" >&9; }
#wait
read TMPRESULTS <&8
[ 000 = "$TMPRESULTS" ]
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"
หมายเหตุพกพา:
ksh
และเชลล์ที่คล้ายกันซึ่งรอคำสั่งไปป์สุดท้ายเท่านั้นที่ต้องการการไม่แสดงwait
ข้อคิดเห็น
ตัวอย่างสุดท้ายใช้printf "%1s" "$?"
แทนecho -n "$?"
เพราะเป็นอุปกรณ์พกพามากกว่า ไม่ใช่ทุกแพลตฟอร์มตีความ-n
อย่างถูกต้อง
printf "$?"
จะทำเช่นกัน แต่printf "%1s"
จับกรณีมุมบางส่วนในกรณีที่คุณเรียกใช้สคริปต์บนแพลตฟอร์มที่แตกหักบางอย่าง (อ่าน: ถ้าคุณเกิดขึ้นกับโปรแกรมparanoia_mode=extreme
มา)
FD 8 และ FD 9 สามารถสูงขึ้นได้บนแพลตฟอร์มที่รองรับหลายหลัก AFAIR เชลล์ที่สอดคล้องกับ POSIX จะต้องรองรับตัวเลขหลักเดียวเท่านั้น
ได้รับการทดสอบกับ Debian 8.2 sh
, bash
, ksh
, ash
, sash
และแม้กระทั่งcsh
ด้วยความระมัดระวังเล็กน้อยสิ่งนี้ควรใช้งานได้:
foo-status=$(mktemp -t)
(foo; echo $? >$foo-status) | bar
foo_status=$(cat $foo-status)
บล็อก 'if' ต่อไปนี้จะทำงานเฉพาะในกรณีที่คำสั่ง 'สำเร็จ'
if command; then
# ...
fi
คุณสามารถเรียกใช้สิ่งนี้:
haconf_out=/path/to/some/temporary/file
if haconf -makerw > "$haconf_out" 2>&1; then
grep -iq "Cluster already writable" "$haconf_out"
# ...
fi
ซึ่งจะเรียกใช้haconf -makerw
และเก็บ stdout และ stderr ไว้ที่ "$ haconf_out" หากค่าที่ส่งคืนจากhaconf
นั้นเป็นจริงบล็อก 'if' จะถูกเรียกใช้งานและgrep
จะอ่าน "$ haconf_out" พยายามจับคู่กับ "Cluster ที่สามารถเขียนได้"
สังเกตว่าท่อทำความสะอาดตัวเองโดยอัตโนมัติ ด้วยการเปลี่ยนเส้นทางคุณจะต้องระมัดระวังในการลบ "$ haconf_out" เมื่อเสร็จแล้ว
ไม่หรูหราเท่าpipefail
แต่เป็นทางเลือกที่ถูกต้องหากฟังก์ชันนี้ไม่สามารถเข้าถึงได้
Alternate example for @lesmana solution, possibly simplified.
Provides logging to file if desired.
=====
$ cat z.sh
TEE="cat"
#TEE="tee z.log"
#TEE="tee -a z.log"
exec 8>&- 9>&-
{
{
{
{ #BEGIN - add code below this line and before #END
./zz.sh
echo ${?} 1>&8 # use exactly 1x prior to #END
#END
} 2>&1 | ${TEE} 1>&9
} 8>&1
} | exit $(read; printf "${REPLY}")
} 9>&1
exit ${?}
$ cat zz.sh
echo "my script code..."
exit 42
$ ./z.sh; echo "status=${?}"
my script code...
status=42
$
(ด้วยการทุบตีอย่างน้อย) รวมกับset -e
หนึ่งสามารถใช้ subshell เพื่อเลียนแบบ pipefail และออกจากข้อผิดพลาดท่ออย่างชัดเจน
set -e
foo | bar
( exit ${PIPESTATUS[0]} )
rest of program
ดังนั้นหากfoo
ล้มเหลวด้วยเหตุผลบางประการ - ส่วนที่เหลือของโปรแกรมจะไม่ถูกเรียกใช้และออกจากสคริปต์พร้อมรหัสข้อผิดพลาดที่เกี่ยวข้อง (สิ่งนี้อนุมานว่าfoo
พิมพ์ข้อผิดพลาดของตัวเองซึ่งเพียงพอที่จะเข้าใจสาเหตุของความล้มเหลว)
แก้ไข : คำตอบนี้ผิด แต่น่าสนใจดังนั้นฉันจะปล่อยไว้เพื่ออ้างอิงในอนาคต
!
คำสั่งลงในกลับรหัสย้อนกลับ
http://tldp.org/LDP/abs/html/exit-status.html
# =========================================================== #
# Preceding a _pipe_ with ! inverts the exit status returned.
ls | bogus_command # bash: bogus_command: command not found
echo $? # 127
! ls | bogus_command # bash: bogus_command: command not found
echo $? # 0
# Note that the ! does not change the execution of the pipe.
# Only the exit status changes.
# =========================================================== #
ls
ไม่ใช่กลับรหัสทางออกของbogus_command