bash: วิธีการเผยแพร่ข้อผิดพลาดในการทดแทนกระบวนการ?


19

ฉันต้องการเชลล์สคริปต์ของฉันล้มเหลวเมื่อใดก็ตามที่คำสั่งดำเนินการกับพวกเขาล้มเหลว

โดยทั่วไปแล้วฉันจะทำกับ:

set -e
set -o pipefail

(ฉันมักจะเพิ่มset -uด้วย)

สิ่งนี้คือไม่มีสิ่งใดในข้างต้นที่ใช้ได้กับการทดแทนกระบวนการ รหัสนี้พิมพ์ว่า "ok" และออกโดยมีรหัสส่งคืน = 0 ในขณะที่ฉันต้องการมันล้มเหลว:

#!/bin/bash -e
set -o pipefail
cat <(false) <(echo ok)

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

วิธีแก้ไขปัญหาของคนจนจะตรวจพบว่าคำสั่งเหล่านั้นเขียนไปยัง stderr (แต่บางคำสั่งเขียนไปยัง stderr ในสถานการณ์ที่ประสบความสำเร็จ)

อีกโซลูชันที่สอดคล้องกับ posix จะใช้ชื่อ pipes แต่ฉันจำเป็นต้อง lauch คำสั่งเหล่านั้น - ใช้ - กระบวนการแทน - เป็น oneliners สร้างขึ้นทันทีจากรหัสรวบรวมและสร้างไปป์ชื่อจะซับซ้อนสิ่ง (คำสั่งพิเศษข้อผิดพลาดในการดักจับสำหรับ ลบออก ฯลฯ )


คุณไม่จำเป็นต้องดักจับข้อผิดพลาดเพื่อลบชื่อไปป์ ความจริงแล้วนั่นไม่ใช่วิธีที่ดีเลย mkfifo pipe; { rm pipe; cat <file; } >pipeพิจารณา คำสั่งนั้นจะค้างไว้จนกว่าตัวอ่านจะเปิดขึ้นpipeเพราะมันเป็นเชลล์ที่ทำopen()เช่นนั้นทันทีที่มีตัวอ่านบนpipeลิงก์ fs pipeเป็นrm'd จากนั้นก็catคัดลอก infile ออกไปยัง descriptor ของเชลล์สำหรับไพพ์นั้น และถ้าคุณต้องการที่จะเผยแพร่ข้อผิดพลาดออกจากกระบวนการย่อย: <( ! : || kill -2 "$$")
mikeserv

ขอบคุณสำหรับเคล็ดลับในการลบท่อที่มีชื่อ น่าเสียดายที่การ$$ทดแทนไม่ได้ผลสำหรับฉันเนื่องจากการแทนที่คำสั่ง ths ไม่ได้ทำตามคำสั่งที่ใช้การทดแทนกระบวนการเสร็จภายในท่อส่งคำสั่งที่เกิดจากรหัส "ไม่ใช่เปลือก" (python) อาจเป็นไปได้ว่าฉันควรสร้าง subprocess ในไพ ธ อนและไพพ์ไปตามโปรแกรม
juanleon

kill -2 0ดังนั้นการใช้งาน
mikeserv

น่าเสียดายที่ "kill -2 0" จะฆ่างูหลาม (สัญญาณ) การเขียนตัวจัดการสัญญาณสำหรับการดำเนินการตรรกะทางธุรกิจภายในแอปพลิเคชันแบบมัลติเธรดไม่ใช่สิ่งที่ฉันหวังว่าจะได้ :-)
juanleon

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

คำตอบ:


6

คุณสามารถแก้ไขปัญหานั้นได้ด้วยตัวอย่างเช่น:

cat <(false || kill $$) <(echo ok)
other_command

เชลล์ย่อยของสคริปต์คือSIGTERMd ก่อนที่คำสั่งที่สองจะสามารถดำเนินการได้ ( other_command) echo okคำสั่งจะถูกดำเนินการ "บางครั้ง" ปัญหาคือว่าการแทนกระบวนการตรงกัน ไม่มีการรับประกันว่าไม่มีkill $$คำสั่งจะถูกดำเนินการก่อนหรือหลังจากecho okคำสั่ง มันเป็นเรื่องของการจัดตารางเวลาระบบปฏิบัติการ

พิจารณา bash script เช่นนี้:

#!/bin/bash
set -e
set -o pipefail
cat <(echo pre) <(false || kill $$) <(echo post)
echo "you will never see this"

ผลลัพธ์ของสคริปต์นั้นสามารถ:

$ ./script
Terminated
$ echo $?
143           # it's 128 + 15 (signal number of SIGTERM)

หรือ:

$ ./script
Terminated
$ pre
post

$ echo $?
143

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

หรือจะแม่นยำยิ่งขึ้น: การเรียกระบบsignal()ของkillutillity ที่ส่งSIGTERMสัญญาณไปยังกระบวนการเชลล์ถูกกำหนดเวลา (หรือถูกส่ง) ในภายหลังหรือเร็วกว่า echo write()syscalls

แต่สคริปต์จะหยุดทำงานและรหัสการออกไม่ใช่ 0 ดังนั้นจึงควรแก้ไขปัญหาของคุณ

อีกวิธีหนึ่งคือใช้ท่อที่มีชื่อสำหรับสิ่งนี้ แต่มันก็ขึ้นอยู่กับสคริปต์ของคุณว่ามันซับซ้อนแค่ไหนที่จะใช้ไพพ์ที่มีชื่อหรือวิธีแก้ปัญหาด้านบน

อ้างอิง:


2

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

โดยทั่วไปฉันสิ้นสุดการทำสิ่งนี้:

command <(subcomand 2>error_file && rm error_file) <(....) ...

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


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

2

ตัวอย่างนี้แสดงให้เห็นถึงวิธีการใช้งานร่วมกับkilltrap

#! /bin/bash
failure ()
{
  echo 'sub process failed' >&2
  exit 1
}
trap failure SIGUSR1
cat < <( false || kill -SIGUSR1 $$ )

แต่killไม่สามารถส่งคืนรหัสจากกระบวนการย่อยของคุณไปยังกระบวนการหลักได้


ฉันชอบรูปแบบนี้กับคำตอบที่ยอมรับ
sehe

1

ในทำนองเดียวกันกับที่คุณจะใช้$PIPESTATUS/ $pipestatusกับเชลล์ POSIX ที่ไม่สนับสนุนคุณสามารถรับสถานะการออกของคำสั่งโดยส่งพวกเขาผ่านไปป์:

unset -v false_status echo_status
{ code=$(
    exec 3>&1 >&4 4>&-
    cat 3>&- <(false 3>&-; echo >&3 "false_status=$?") \
             <(echo ok 3>&-; echo >&3 "echo_status=$?")
);} 4>&1
cat_status=$?
eval "$code"
printf '%s_code=%d\n' cat   "$cat_status" \
                      false "$false_status" \
                      echo  "$echo_status"

ซึ่งจะช่วยให้:

ok
cat_code=0
false_code=1
echo_code=0

หรือคุณสามารถใช้pipefailและดำเนินการทดแทนกระบวนการด้วยมือเหมือนที่คุณทำกับเชลล์ที่ไม่รองรับ:

set -o pipefail
{
  false <&5 5<&- | {
    echo OK <&5 5<&- | {
      cat /dev/fd/3 /dev/fd/4
    } 4<&0 <&5 5<&-
  } 3<&0
} 5<&0
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.