พฤติกรรมที่ถูกต้องของกับดัก EXIT และ ERR เมื่อใช้ `set -eu`


27

ฉันสังเกตเห็นพฤติกรรมแปลก ๆ บางอย่างเมื่อใช้set -e( errexit), set -u( nounset) พร้อมกับดัก ERR และ EXIT ดูเหมือนว่าพวกเขาเกี่ยวข้องกันดังนั้นการใส่คำถามเหล่านั้นลงในคำถามเดียวก็สมเหตุสมผล

1) set -uไม่เรียกใช้กับดัก ERR

  • รหัส:

    #!/bin/bash
    trap 'echo "ERR (rc: $?)"' ERR
    set -u
    echo ${UNSET_VAR}
    
  • คาดว่า: มีการเรียกใช้กับดัก ERR, RC! = 0
  • จริง: ไม่ได้เรียกกับแทร็บ ERR , RC == 1
  • หมายเหตุ: set -eไม่เปลี่ยนผลลัพธ์

2) การใช้set -euรหัสออกในกับดักของ EXIT คือ 0 แทนที่จะเป็น 1

  • รหัส:

    #!/bin/bash
    trap 'echo "EXIT (rc: $?)"' EXIT
    set -eu
    echo ${UNSET_VAR}
    
  • คาดว่า: EXIT กับดักถูกเรียกRC == 1
  • จริง: ออกจากกับดักเรียกว่าRC == 0
  • หมายเหตุ: เมื่อใช้set +eRC == 1. แทร็ก EXIT จะส่งคืน RC ที่เหมาะสมเมื่อคำสั่งอื่นส่งข้อผิดพลาด
  • แก้ไข:มีโพสต์ SO ในหัวข้อนี้ที่มีความคิดเห็นที่น่าสนใจแนะนำว่านี่อาจเกี่ยวข้องกับเวอร์ชัน Bash ที่ใช้อยู่ ทดสอบตัวอย่างข้อมูลนี้ด้วย Bash 4.3.11 ให้ผลเป็น RC = 1 ดีกว่า น่าเสียดายที่การอัปเกรด Bash (จาก 3.2.51) ในโฮสต์ทั้งหมดเป็นไปไม่ได้ในขณะนี้ดังนั้นเราจึงต้องหาวิธีแก้ปัญหาอื่น ๆ

ใครสามารถอธิบายพฤติกรรมเหล่านี้ได้บ้าง

การค้นหาหัวข้อเหล่านี้ไม่ประสบความสำเร็จมากซึ่งค่อนข้างน่าแปลกใจเนื่องจากจำนวนโพสต์ในการตั้งค่า Bash และกับดัก มีเธรดฟอรัมหนึ่งหัวข้อแต่ข้อสรุปค่อนข้างไม่พอใจ


3
ฉันคิดว่าตั้งแต่ 4 bashยากจนกับมาตรฐานและเริ่มวางกับดักใน subshells กับดักควรถูกประหารชีวิตในสภาพแวดล้อมเดียวกันจากที่ใดที่มันกลับมา แต่bashก็ไม่ได้ทำแบบนั้นสักพัก
mikeserv

1
รอสักครู่ - คุณต้องการคำตอบหรือคำอธิบายไหม? และถ้าคุณต้องการวิธีการแก้ปัญหา คุณต้องการเกิดอะไรขึ้น set -eและset -uได้รับการออกแบบมาเป็นพิเศษเพื่อฆ่าเชลล์สคริปต์ การใช้พวกเขาในสภาวะที่อาจทำให้แอปพลิเคชันของพวกเขาจะฆ่าเชลล์สคริปต์ ไม่มีสิ่งใดมาขวางกั้นได้นอกจากจะไม่ใช้มันและเพื่อทำการทดสอบแทนเงื่อนไขเหล่านั้นเมื่อนำไปใช้ในลำดับของรหัส set -euดังนั้นโดยทั่วไปคุณสามารถเขียนดีเปลือกรหัสหรือคุณสามารถใช้
mikeserv

2
ที่จริงแล้วฉันกำลังมองหาทั้งคู่เพราะฉันไม่สามารถหาข้อมูลที่เพียงพอว่าทำไม-uจะไม่เรียกใช้กับดัก ERR (มันเป็นข้อผิดพลาดดังนั้นจึงไม่ควรเรียกกับดัก) หรือรหัสข้อผิดพลาดคือ 0 แทน 1 หลังดูเหมือนจะเป็นข้อผิดพลาดสิ่งที่ได้รับการแก้ไขในรุ่นที่ใหม่กว่าดังนั้นนั่นคือ แต่ส่วนแรกค่อนข้างยากที่จะเข้าใจหากคุณไม่ได้ตระหนักว่าข้อผิดพลาดในการประเมินเชลล์ (การขยายพารามิเตอร์) และข้อผิดพลาดจริงในคำสั่งนั้นดูเหมือนจะเป็นสองสิ่งที่แตกต่างกัน สำหรับวิธีการแก้ปัญหาที่คุณแนะนำคือตอนนี้ฉันกำลังพยายามหลีกเลี่ยง-euและตรวจสอบด้วยตนเองเมื่อจำเป็น
dvdgsng

1
@dvdsng - ดี นั่นเป็นวิธีที่จะไป - คุณควรโพสต์สคริปต์ของคุณเมื่อคุณทำตามคำตอบและให้รางวัลด้วยตัวคุณเอง ฉันไม่ชอบตัวเลือกเหล่านั้น - พวกเขาไม่อนุญาตให้มีการจัดการข้อยกเว้นในทางที่ปลอดภัยใด ๆ
mikeserv

1
@dvdsng - โดยที่ตัวเลือกใดตัวเลือกหนึ่งเหล่านั้นมีประโยชน์แม้ว่าอยู่ในบริบทย่อย และดังนั้นจึงเป็นไปได้ว่าสิ่งที่คุณใช้ก่อนหน้านี้สามารถแปลเป็นบริบทย่อยเช่น: (set -u; : $UNSET_VAR)และที่คล้ายกัน สิ่งต่าง ๆ เช่นนี้ก็ดีเช่นกัน - คุณสามารถทำอะไรหลาย ๆ อย่าง&&ได้(set -e; mkdir dir; cd dir; touch dirfile)บ้าง: ถ้าคุณได้ดริฟท์ของฉัน เป็นเพียงว่าสิ่งเหล่านั้นถูกควบคุมบริบท - เมื่อคุณตั้งค่าเป็นตัวเลือกระดับโลก มักจะมีวิธีแก้ปัญหาที่มีประสิทธิภาพมากกว่า
mikeserv

คำตอบ:


15

จากman bash:

  • set -u
    • ปฏิบัติกับตัวแปรที่ไม่มีการตั้งค่าและพารามิเตอร์อื่นที่ไม่ใช่พารามิเตอร์พิเศษ "@"และ"*"เป็นข้อผิดพลาดเมื่อดำเนินการขยายพารามิเตอร์ หากมีการพยายามขยายส่วนขยายในตัวแปรหรือพารามิเตอร์ที่ไม่ได้ตั้งค่าเชลล์จะพิมพ์ข้อความแสดงข้อผิดพลาดและหากไม่ใช่แบบไม่-iโต้ตอบให้ออกจากสถานะไม่เป็นศูนย์

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

  • ผลที่ตามมาของข้อผิดพลาดของเชลล์ :
    • ข้อผิดพลาดการขยายตัวเป็นสิ่งหนึ่งที่เกิดขึ้นเมื่อการขยายเปลือกที่กำหนดไว้ในการขยาย Word ที่จะดำเนินการ(ตัวอย่างเช่น"${x!y}"เพราะ!ไม่ได้เป็นผู้ประกอบการที่ถูกต้อง) ; การนำไปใช้อาจถือว่าสิ่งเหล่านี้เป็นข้อผิดพลาดทางไวยากรณ์ถ้ามันสามารถตรวจพบพวกเขาในระหว่าง tokenization มากกว่าในระหว่างการขยายตัว
    • [A] n เชลล์เชิงโต้ตอบจะต้องเขียนข้อความวินิจฉัยไปยังข้อผิดพลาดมาตรฐานโดยไม่ต้องออก

ยังมาจากman bash:

  • trap ... ERR
    • หาก sigspec คือERRคำสั่งargจะถูกดำเนินการเมื่อใดก็ตามที่ไปป์ไลน์(ซึ่งอาจประกอบด้วยคำสั่งง่าย ๆ เดียว)รายการหรือคำสั่งผสมส่งกลับสถานะทางออกที่ไม่ใช่ศูนย์โดยขึ้นอยู่กับเงื่อนไขดังต่อไปนี้:
      • ERRกับดักจะไม่ทำงานถ้าคำสั่งล้มเหลวเป็นส่วนหนึ่งของรายการคำสั่งทันทีดังต่อไปนี้whileหรือuntilคำหลัก ...
      • ... ส่วนหนึ่งของการทดสอบใน ifแถลงการณ์ ...
      • ... ส่วนหนึ่งของคำสั่งที่ดำเนินการใน&&หรือ||รายการยกเว้นคำสั่งที่ตามมาสุดท้าย&&หรือ||...
      • ... คำสั่งใด ๆ ในไปป์ไลน์ แต่สุดท้าย ...
      • ... !หรือถ้าค่าตอบแทนของคำสั่งจะถูกคว่ำโดยใช้
    • เงื่อนไขเหล่านี้เป็นไปตามเงื่อนไขของตัวเลือกerrexit -e

หมายเหตุข้างต้นว่ากับดักERRทั้งหมดเกี่ยวกับการประเมินผลตอบแทนของคำสั่งอื่น ๆ แต่เมื่อเกิดข้อผิดพลาดในการขยายตัวจะไม่มีคำสั่งรันเพื่อส่งคืนสิ่งใด ในตัวอย่างของคุณecho ไม่เคยเกิดขึ้นเพราะในขณะที่เชลล์ประเมินและขยายอาร์กิวเมนต์มันพบ-uตัวแปร nset ซึ่งระบุโดยตัวเลือกเชลล์อย่างชัดเจนเพื่อให้เกิดการออกจากเชลล์สคริปต์ปัจจุบันในทันที

ดังนั้นกับดักของEXITถ้ามีจะถูกดำเนินการและเชลล์ออกจากพร้อมกับข้อความวินิจฉัยและสถานะการออกที่ไม่ใช่ 0 - อย่างที่ควรจะเป็น

สำหรับRC: 0สิ่งที่ผมคาดหวังว่าเป็นปัญหาที่รุ่นที่เฉพาะเจาะจงของบางชนิด - อาจจะทำอย่างไรกับสองทริกเกอร์สำหรับEXITที่เกิดขึ้นในเวลาเดียวกันและอีกคนหนึ่งได้รับรหัสทางออกอื่น ๆ(ซึ่งไม่ควรจะเกิดขึ้น) และอย่างไรก็ตามด้วยbashไบนารีที่ทันสมัยตามที่ติดตั้งโดยpacman:

bash <<\IN
    printf "shell options:\t$-\n"
    trap 'echo "EXIT (rc: $?)"' EXIT
    set -eu
    echo ${UNSET_VAR}
IN

ฉันเพิ่มบรรทัดแรกเพื่อให้คุณเห็นว่าเงื่อนไขของเชลล์เป็นของเชลล์สคริปต์ - มันไม่ได้เป็นแบบโต้ตอบ ผลลัพธ์คือ:

shell options:  hB
bash: line 4: UNSET_VAR: unbound variable
EXIT (rc: 1)

นี่คือหมายเหตุที่เกี่ยวข้องจากการเปลี่ยนแปลงล่าสุด :

  • แก้ไขข้อผิดพลาดที่ทำให้คำสั่งแบบอะซิงโครนัสไม่สามารถตั้งค่า$?ได้อย่างถูกต้อง
  • แก้ไขข้อบกพร่องที่ทำให้เกิดข้อความแสดงข้อผิดพลาดที่เกิดจากข้อผิดพลาดใน การขยายforคำสั่งให้มีหมายเลขบรรทัดที่ไม่ถูกต้อง
  • แก้ไขข้อผิดพลาดที่ทำให้SIGINTและSIGQUITไม่สามารถtrappable ในคำสั่ง subshell แบบอะซิงโครนัส
  • แก้ไขปัญหาเกี่ยวกับการจัดการอินเตอร์รัปต์ที่ทำให้SIGINTตัวที่สองขึ้นไปถูกละเว้นโดยเชลล์แบบโต้ตอบ
  • เปลือกบล็อกใบเสร็จรับเงินไม่มีสัญญาณในขณะที่ใช้trapรถยกสำหรับสัญญาณเหล่านั้นและช่วยให้ส่วนใหญ่ trapขนย้ายวัสดุที่จะทำงานซ้ำ (วิ่งtrapรถขนในขณะที่trapตัวจัดการการดำเนินการ)

ฉันคิดว่าอาจเป็นรายการสุดท้ายหรือรายการแรกที่มีความเกี่ยวข้องมากที่สุดหรืออาจเป็นการรวมกันของสองรายการ trapจัดการโดยธรรมชาติของมันมากไม่ตรงกันเพราะงานทั้งหมดของมันคือการรอและจัดการสัญญาณไม่ตรงกัน และคุณทริกเกอร์สองอย่างพร้อมกันด้วย-euและ$UNSET_VARและ

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


ขอขอบคุณสำหรับคำอธิบายว่าการจัดการพารามิเตอร์ขยายแตกต่างกันอย่างไร ที่ล้างสิ่งต่าง ๆ มากมายให้ฉัน
dvdgsng

ฉันให้สิทธิ์แก่คุณเพราะคำอธิบายของคุณมีประโยชน์มากที่สุด
dvdgsng

@dvdgsng - Gracias จากความอยากรู้อยากเห็นคุณเคยคิดกับวิธีแก้ปัญหาของคุณไหม?
mikeserv

9

(ฉันใช้ bash 4.2.53) สำหรับส่วนที่ 1 หน้า bash man จะบอกว่า "ข้อความข้อผิดพลาดจะถูกเขียนลงในข้อผิดพลาดมาตรฐานและเชลล์ที่ไม่มีการโต้ตอบจะออก" ไม่ได้บอกว่าจะเรียกกับดัก ERR แม้ว่าฉันจะเห็นว่ามันจะมีประโยชน์ถ้ามัน

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

#!/bin/bash
trap 'rc=$?; echo "ERR at line ${LINENO} (rc: $rc)"; exit $rc' ERR
trap 'rc=$?; echo "EXIT (rc: $rc)"; exit $rc' EXIT
set -u
set -E # export trap to functions

cmd(){
 echo "args=$*"
 echo ${UNSET_VAR}
 echo hello
}
oops(){
 rc=$?
 echo "$@"
 return $rc # provoke ERR trap
}

exec 3>&1 # copy stdin to use in $()
if output=$(cmd "$@" 2>&1 >&3) # collect stderr, not stdout 
then    echo ok
else    oops "fail: $output"
fi

ที่ทุบตีของฉันฉันได้รับ

./script my stuff; echo "exit was $?"
args=my stuff
fail: ./script: line 9: UNSET_VAR: unbound variable
ERR at line 15 (rc: 1)
EXIT (rc: 1)
exit was 1

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