ชุด -e หมายถึงอะไรในสคริปต์ทุบตี?


713

ฉันกำลังศึกษาเนื้อหาของไฟล์preinstนี้ที่สคริปต์ดำเนินการก่อนแพ็กเกจนั้นจะถูกแตกจากไฟล์ Debian archive (.deb)

สคริปต์มีรหัสต่อไปนี้:

#!/bin/bash
set -e
# Automatically added by dh_installinit
if [ "$1" = install ]; then
   if [ -d /usr/share/MyApplicationName ]; then
     echo "MyApplicationName is just installed"
     return 1
   fi
   rm -Rf $HOME/.config/nautilus-actions/nautilus-actions.conf
   rm -Rf $HOME/.local/share/file-manager/actions/*
fi
# End automatically added section

ข้อความค้นหาแรกของฉันเกี่ยวกับบรรทัด:

set -e

ฉันคิดว่าสคริปต์ส่วนที่เหลือนั้นค่อนข้างเรียบง่าย: มันตรวจสอบว่าตัวจัดการแพคเกจ Debian / Ubuntu กำลังเรียกใช้การติดตั้งหรือไม่ หากเป็นเช่นนั้นจะตรวจสอบว่ามีการติดตั้งแอปพลิเคชันของฉันในระบบหรือไม่ หากมีสคริปต์จะพิมพ์ข้อความ"MyApplicationName เพิ่งติดตั้ง"และสิ้นสุด ( return 1หมายความว่าลงท้ายด้วย "ข้อผิดพลาด" ใช่ไหม?)

หากผู้ใช้ขอให้ระบบแพคเกจ Debian / Ubuntu ติดตั้งแพคเกจของฉันสคริปต์ก็จะลบสองไดเรกทอรี

ถูกหรือว่าฉันทำอะไรหายไป?



46
เหตุผลที่คุณไม่พบสิ่งนี้ใน google: -e ในข้อความค้นหาของคุณถูกตีความว่าเป็นการปฏิเสธ ลองใช้คำค้นหาต่อไปนี้: bash set "-e"
Maleev

3
@twalberg เมื่อฉันถามตัวเองด้วยคำถามเดียวกันฉันกำลังดูman set
Sedat Kilinc

4
หากคุณกำลังมองหาวิธีปิดใช้งานให้สลับไปที่ส่วนนำหน้าเป็นบวก:set +e
Tom Saleeba

@ twalberg แต่การถามคนจริงๆมันน่าสนใจมากกว่าแค่ขอจากหุ่นยนต์ ;-)
vdegenne

คำตอบ:


797

จากhelp set:

  -e  Exit immediately if a command exits with a non-zero status.

แต่ก็ถือว่าการปฏิบัติที่ไม่ดีโดยบางคน (bash FAQ และ irc freenode #bash ผู้เขียนคำถามที่พบบ่อย) แนะนำให้ใช้:

trap 'do_something' ERR

เพื่อเรียกใช้do_somethingฟังก์ชันเมื่อเกิดข้อผิดพลาด

ดูhttp://mywiki.wooledge.org/BashFAQ/105


14
สิ่งที่ do_something จะเป็นอย่างไรถ้าฉันต้องการความหมายเช่นเดียวกับ "ออกทันทีถ้าคำสั่งออกด้วยสถานะที่ไม่เป็นศูนย์"?
CMCDragonkai

71
trap 'exit' ERR
chepner

12
ERRดักไม่ได้สืบเชื้อสายมาจากฟังก์ชั่นเปลือกดังนั้นหากคุณมีฟังก์ชั่น set -o errtraceหรือset -Eจะช่วยให้คุณสามารถตั้งค่าเพียงครั้งเดียวกับดักและใช้มันทั่วโลก
ykay

31
ไม่trap 'exit' ERRทำอะไรที่แตกต่างจากset -e?
Andy

22
ถ้าเป็นการฝึกที่ไม่ดีแล้วทำไมมันถึงใช้ในแพ็คเกจ Debian ?
phuclv

98

set -eหยุดการทำงานของสคริปต์หากคำสั่งหรือไพพ์ไลน์มีข้อผิดพลาด - ซึ่งตรงกันข้ามกับพฤติกรรมเชลล์เริ่มต้นซึ่งเป็นการละเว้นข้อผิดพลาดในสคริปต์ พิมพ์help setเทอร์มินัลเพื่อดูเอกสารประกอบสำหรับคำสั่งในตัวนี้


44
มันจะหยุดการทำงานเฉพาะเมื่อคำสั่งสุดท้ายในไปป์ไลน์มีข้อผิดพลาด มีตัวเลือกเฉพาะของ Bash set -o pipefailซึ่งสามารถใช้เพื่อเผยแพร่ข้อผิดพลาดเพื่อให้ค่าส่งคืนของคำสั่งไปป์ไลน์นั้นไม่ใช่ศูนย์หากหนึ่งในคำสั่งก่อนหน้านี้ออกจากสถานะไม่เป็นศูนย์
Anthony Geoghegan

2
โปรดทราบว่า-o pipefailหมายถึงเฉพาะสถานะการออกของ-o errexitคำสั่งที่ไม่ใช่ศูนย์แรก (เช่นการแก้ไขข้อผิดพลาดในแง่) ของไปป์ไลน์จะแพร่กระจายไปยังจุดสิ้นสุด ส่วนที่เหลืออีกคำสั่งในท่อยังคงทำงานset -o errexitแม้จะมี ตัวอย่างเช่นecho success | cat - <(echo piping); echo continuesที่echo successแสดงถึงการประสบความสำเร็จ แต่ทำผิดคำสั่งจะพิมพ์success, pipingและcontinuesแต่false | cat - <(echo piping); echo continuesด้วยความfalseที่เป็นตัวแทนของคำสั่งในขณะนี้ erroring เงียบ ๆ จะยังคงพิมพ์pipingก่อนที่จะออก
bb010g

54

เป็นต่อทุบตี - ชุด Builtinคู่มือถ้า-e/ errexitการตั้งค่าออกจากหอยทันทีถ้าท่อประกอบด้วยเดียวคำสั่งง่ายๆ , รายการหรือสารประกอบที่คำสั่งผลตอบแทนสถานะที่ไม่ใช่ศูนย์

โดยค่าเริ่มต้นสถานะการออกของไปป์ไลน์เป็นสถานะออกของคำสั่งสุดท้ายในไปป์ไลน์ยกเว้นว่าpipefailตัวเลือกนั้นเปิดใช้งานอยู่ (โดยค่าเริ่มต้นจะถูกปิดใช้งาน)

ถ้าเป็นเช่นนั้นสถานะการส่งคืนของไปป์ของคำสั่งสุดท้าย (ขวาสุด) เพื่อออกด้วยสถานะที่ไม่เป็นศูนย์หรือเป็นศูนย์หากคำสั่งทั้งหมดออกจากที่สำเร็จ

หากคุณต้องการดำเนินการบางอย่างเมื่อออกให้ลองกำหนดtrapตัวอย่างเช่น:

trap onexit EXIT

ซึ่งonexitเป็นหน้าที่ของคุณที่จะทำบางสิ่งบางอย่างออกเช่นนี้ซึ่งเป็นพิมพ์ที่เรียบง่ายกองติดตาม :

onexit(){ while caller $((n++)); do :; done; }

มีตัวเลือกที่คล้ายกัน-E/errtraceซึ่งจะดักจับบน ERR แทนเช่น:

trap onerr ERR

ตัวอย่าง

ตัวอย่างสถานะ Zero:

$ true; echo $?
0

ตัวอย่างสถานะที่ไม่เป็นศูนย์:

$ false; echo $?
1

การปฏิเสธตัวอย่างสถานะ:

$ ! false; echo $?
0
$ false || true; echo $?
0

ทดสอบเมื่อpipefailถูกปิดการใช้งาน:

$ bash -c 'set +o pipefail -e; true | true | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; false | false | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; true | true | false; echo success'; echo $?
1

ทดสอบกับpipefailการเปิดใช้งาน:

$ bash -c 'set -o pipefail -e; true | false | true; echo success'; echo $?
1

54

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

ตัวอย่างเช่นสมมติว่าฉันมีเชลล์สคริปต์outer-test.sh:

#!/bin/sh
set -e
./inner-test.sh
exit 62;

รหัสสำหรับinner-test.shคือ:

#!/bin/sh
exit 26;

เมื่อฉันเรียกใช้outer-script.shจากบรรทัดคำสั่งสคริปต์ตัวนอกของฉันจะจบลงด้วยรหัสทางออกของสคริปต์ภายใน:

$ ./outer-test.sh
$ echo $?
26

10

ฉันเชื่อว่าความตั้งใจนั้นมีไว้เพื่อให้สคริปต์ที่เป็นปัญหาล้มเหลวอย่างรวดเร็ว

ในการทดสอบด้วยตัวเองเพียงพิมพ์set -eที่ bash prompt lsตอนนี้ลองใช้ คุณจะได้รับรายชื่อไดเรกทอรี lsdตอนนี้พิมพ์ คำสั่งนั้นไม่ได้รับการยอมรับและจะส่งคืนรหัสข้อผิดพลาดและเพื่อให้ bash พรอมต์ของคุณจะปิด (เนื่องจากset -e)

ตอนนี้เพื่อให้เข้าใจสิ่งนี้ในบริบทของ 'สคริปต์' ใช้สคริปต์ง่าย ๆ นี้:

#!/bin/bash 
# set -e

lsd 

ls

หากคุณเรียกใช้เหมือนเดิมคุณจะได้รับรายชื่อไดเรกทอรีจากlsบรรทัดสุดท้าย หากคุณบรรทัดเหล่าและเรียกใช้อีกครั้งคุณจะไม่เห็นไดเรกทอรีรายชื่อทุบตีหยุดการประมวลผลเมื่อพบข้อผิดพลาดจากset -elsd


คำตอบนี้เพิ่มข้อมูลเชิงลึกหรือข้อมูลที่ไม่ได้ให้ไว้กับผู้อื่นในคำถามหรือไม่?
Charles Duffy

6
ฉันคิดว่ามันให้คำอธิบายที่ชัดเจนและกระชับเกี่ยวกับฟังก์ชั่นที่ไม่ได้มีอยู่ในคำตอบอื่น ๆ ไม่มีอะไรเพิ่มเติมเน้นเพียงกว่าการตอบสนองอื่น ๆ
Kallin Nagelberg

9

นี่เป็นคำถามเก่า แต่ไม่มีคำตอบใดในที่นี้ที่กล่าวถึงการใช้set -eaka set -o errexitในสคริปต์การจัดการแพ็คเกจ Debian การใช้ตัวเลือกนี้มีผลบังคับใช้ในสคริปต์เหล่านี้ตามนโยบายของเดเบียน เห็นได้ชัดว่ามีเจตนาที่จะหลีกเลี่ยงความเป็นไปได้ของเงื่อนไขข้อผิดพลาดที่ไม่สามารถจัดการได้

สิ่งนี้หมายความว่าในทางปฏิบัติคือคุณต้องเข้าใจภายใต้เงื่อนไขที่คำสั่งที่คุณเรียกใช้อาจส่งคืนข้อผิดพลาดและจัดการข้อผิดพลาดเหล่านั้นอย่างชัดเจน

gotchas ทั่วไปเป็นเช่นdiff(ส่งกลับข้อผิดพลาดเมื่อมีความแตกต่าง) และgrep(กลับข้อผิดพลาดเมื่อไม่มีการแข่งขัน) คุณสามารถหลีกเลี่ยงข้อผิดพลาดด้วยการจัดการที่ชัดเจน:

diff this that ||
  echo "$0: there was a difference" >&2
grep cat food ||
  echo "$0: no cat in the food" >&2

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

หากไม่มีการจัดการที่ชัดเจนมีความจำเป็นหรือมีประโยชน์จริงๆอย่าทำอะไรเลย:

diff this that || true
grep cat food || :

(การใช้:คำสั่ง no-op ของเชลล์ค่อนข้างคลุมเครือเล็กน้อย แต่เห็นได้ทั่วไป)

เพียงเพื่อย้ำ

something || other

เป็นการจดชวเลข

if something; then
    : nothing
else
    other
fi

นั่นคือเราotherควรบอกว่าควรจะทำงานถ้าหากsomethingล้มเหลว คำสั่ง long flow if(และคำสั่งการควบคุมการไหลของเชลล์อื่น ๆ เช่นwhile, until) ก็เป็นวิธีที่ถูกต้องในการจัดการข้อผิดพลาด (แน่นอน, ถ้าไม่ใช่, เชลล์สคริปต์ด้วยset -eไม่สามารถมีคำสั่งการควบคุมการไหล!)

และเพื่อให้ชัดเจนในกรณีที่ไม่มีตัวจัดการเช่นนี้set -eจะทำให้สคริปต์ทั้งหมดล้มเหลวทันทีด้วยข้อผิดพลาดหากdiffพบความแตกต่างหรือหากgrepไม่พบคู่ที่ตรงกัน

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

find things | grep .
sed -e 's/o/me/' stuff | grep ^

ควรสังเกตว่าสถานะการออกของไปป์ไลน์เป็นสถานะออกของคำสั่งสุดท้ายในไปป์ไลน์นั้น ดังนั้นคำสั่งข้างต้นจะปกปิดสถานะของfindและsedโดยสมบูรณ์และบอกเพียงว่าคุณgrepประสบความสำเร็จในที่สุด

(แน่นอนว่า Bash มีset -o pipefailแต่สคริปต์แพคเกจ Debian ไม่สามารถใช้คุณสมบัติ Bash ได้นโยบายกำหนดให้ใช้ POSIX shสำหรับสคริปต์เหล่านี้อย่างแน่นหนาแม้ว่าจะไม่ใช่กรณีนี้เสมอไป)

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


นี่คือคำตอบที่ยอดเยี่ยม และส่งเสริมการปฏิบัติที่ดีที่สุด ฉันมีปัญหาเดียวกันจากคำสั่ง GREP อย่างแน่นอนและฉันไม่ต้องการลบ 'set -e'
Minnie

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