'set -e' ทำอะไรและทำไมจึงอาจถือว่าเป็นอันตราย


147

คำถามนี้ปรากฏในแบบทดสอบก่อนการสัมภาษณ์และมันทำให้ฉันบ้า ทุกคนสามารถตอบคำถามนี้และทำให้ฉันสบายใจได้ไหม? แบบทดสอบไม่มีการอ้างอิงถึงเชลล์เฉพาะ แต่รายละเอียดงานมีไว้สำหรับ unix sa คำถามก็คือ ...

'set -e' ทำอะไรและทำไมจึงอาจถือว่าเป็นอันตราย


นี่คือหัวข้อที่เกี่ยวข้อง: stackoverflow.com/questions/64786/error-handling-in-bash/…
Stefan Lasiewski

คำตอบ:


154

set -e ทำให้เชลล์ออกหากคำสั่งย่อยหรือไพพ์ไลน์ส่งคืนสถานะที่ไม่ใช่ศูนย์

คำตอบที่ผู้สัมภาษณ์อาจมองหาคือ:

อาจเป็นอันตรายหากใช้ "set -e" เมื่อสร้างสคริปต์ init.d:

จากhttp://www.debian.org/doc/debian-policy/ch-opersys.html 9.3.2 -

ระวังการใช้ set -e ในสคริปต์ init.d การเขียนสคริปต์ init.d ที่ถูกต้องต้องยอมรับสถานะข้อผิดพลาดต่าง ๆ เมื่อ daemons กำลังทำงานอยู่หรือหยุดทำงานแล้วโดยไม่ยกเลิกสคริปต์ init.d และไลบรารีฟังก์ชัน init.d ทั่วไปไม่ปลอดภัยที่จะเรียกด้วย set -e สำหรับสคริปต์ init.d มักจะไม่ใช้ set -e และตรวจสอบผลลัพธ์ของแต่ละคำสั่งแยกกันแทน

นี่เป็นคำถามที่ถูกต้องจากมุมมองของผู้สัมภาษณ์เพราะเป็นการประเมินความรู้เกี่ยวกับการทำงานของสคริปต์ระดับเซิร์ฟเวอร์และระบบอัตโนมัติ


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

แม้ว่าฉันเห็นด้วยกับstackoverflow.com/questions/78497/ …ฉันคิดว่ารูปแบบนี้เหมาะสมกับการทุบตีสำหรับการตรวจสอบ init สั้น ๆ และการบันทึกหรือสภาพแวดล้อมอื่น ๆ ในการปะแก้ลิงก่อนที่จะประมวลผลงานเป้าหมายเราใช้นโยบายเดียวกันนี้สำหรับภาพนักเทียบท่าของเรา สคริปต์ init
joshperry

33

จากbash(1):

          -e      Exit immediately if a pipeline (which may consist  of  a
                  single  simple command),  a subshell command enclosed in
                  parentheses, or one of the commands executed as part  of
                  a  command  list  enclosed  by braces (see SHELL GRAMMAR
                  above) exits with a non-zero status.  The shell does not
                  exit  if  the  command that fails is part of the command
                  list immediately following a  while  or  until  keyword,
                  part  of  the  test  following  the  if or elif reserved
                  words, part of any command executed in a && or  ││  list
                  except  the  command  following  the final && or ││, any
                  command in a pipeline but the last, or if the  command’s
                  return  value  is being inverted with !.  A trap on ERR,
                  if set, is executed before the shell exits.  This option
                  applies to the shell environment and each subshell envi-
                  ronment separately (see  COMMAND  EXECUTION  ENVIRONMENT
                  above), and may cause subshells to exit before executing
                  all the commands in the subshell.

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


1
ซ่อนเร้นปัญหาจริง / อื่น ๆ ใช่ฉันเดาว่าฉันเห็นว่า ... ฉันกำลังดิ้นรนเพื่อหาตัวอย่างของการเป็นอันตรายเช่นกัน ...
cpbills

มี manpage สำหรับset! builtin(1)ฉันมักจะจบลงที่ ขอบคุณ
Jared Beck

ว่าฉันจะรู้ว่าเอกสารสำหรับsetคือการ befound ในman 1 bash?? และใครจะหา-eตัวเลือกสำหรับsetคำหลักในหน้าคู่มือซึ่งมีขนาดใหญ่มากได้อย่างไร คุณไม่สามารถ/-eค้นหาได้ที่นี่
phil294

@Blauhirn ใช้งานhelp set
phil294

19

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

some code
set -e     # same as set -o errexit
more code  # exit on non-zero status
set +e     # same as set +o errexit
more code  # no exit on non-zero status

สิ่งที่น่าสังเกตก็คือในส่วนของ Bash man page ในtrapคำสั่งซึ่งอธิบายวิธีการset -eทำงานภายใต้สถานการณ์บางอย่าง

แทร็บ ERR จะไม่ถูกดำเนินการหากคำสั่งที่ล้มเหลวเป็นส่วนหนึ่งของรายการคำสั่งทันทีหลังจากนั้นสักครู่หรือจนกว่าคำสำคัญส่วนหนึ่งของการทดสอบในคำสั่ง if ส่วนหนึ่งของคำสั่งที่ดำเนินการในรายการ && หรือ⎪⎪หรือถ้าคำสั่ง ค่าที่ส่งคืนจะถูกคว่ำผ่าน! เงื่อนไขเหล่านี้เป็นไปตามเงื่อนไขของตัวเลือก errexit

ดังนั้นจึงมีเงื่อนไขบางประการที่สถานะที่ไม่ใช่ศูนย์จะไม่ทำให้เกิดการออก

ฉันคิดว่าอันตรายไม่เข้าใจเมื่อset -eเข้ามาเล่นและเมื่อมันไม่ได้และพึ่งพามันอย่างไม่ถูกต้องภายใต้สมมติฐานที่ไม่ถูกต้อง

โปรดดูBashFAQ / 105 เหตุใดจึงไม่ตั้งค่า -e (หรือตั้งค่า -o errexit หรือ trap ERR) ทำตามที่ฉันคาดไว้


ในขณะที่เบี่ยงเบนไปจากคำถามเล็กน้อยคำตอบนี้เป็นสิ่งที่ฉันกำลังมองหา: ปิดมันเพื่อเพียงส่วนเล็ก ๆ ของสคริปต์!
Stephan Bijzitter

13

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

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

ฉันเคยเข้าร่วมสัมภาษณ์งาน 40 ครั้งในช่วง 2 ปีที่ผ่านมาและบางครั้งผู้สัมภาษณ์ถามคำถามที่ผิดหรือมีคำตอบที่ผิด

หรือบางทีมันอาจเป็นคำถามที่หลอกลวงซึ่งอาจจะง่อย แต่ก็ไม่ได้คาดไม่ถึงเลย

หรือนี่อาจเป็นคำอธิบายที่ดี: http://www.mail-archive.com/debian-bugs-dist@lists.debian.org/msg473314.html


1
+1 ฉันอยู่ในเรือลำเดียวกันและบทสัมภาษณ์ 'ทางเทคนิค' มากมายที่ฉันติดต่อด้วยนั้นเป็นเรื่องที่เข้าใจผิดอย่างน้อย
cpbills

1
ว้าว. ค้นหาได้ดีในรายการเดเบียน +1 สำหรับความไม่รู้ของคำถามสัมภาษณ์ ฉันจำได้ว่าการโต้กลับคำตอบ netback หนึ่งครั้งตั้งแต่ฉันแน่ใจว่าถูกต้อง 100% พวกเขาพูดว่าไม่ ฉันกลับบ้านแล้วไปหาฉันฉันพูดถูก
egorgry

9

set -e บอก bash ในสคริปต์ให้ออกเมื่อใดก็ตามที่ส่งคืนค่าส่งคืนที่ไม่เป็นศูนย์

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


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

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

จุดที่ยอดเยี่ยม @cpbills ฉันยังไม่เห็นการใช้งานสำหรับ set -e ในสคริปต์และอาจเป็นสาเหตุที่ทำให้ฉันงงมาก ฉันต้องการโพสต์คำถามทั้งหมดเนื่องจากมีบางคำถามที่ดีจริงๆและบางคำถามก็แปลกประหลาดและสุ่มตัวอย่างเช่นนี้
egorgry

ผมเคยเห็นมันในงานระบบ cron มากมาย ...
แอนดรู

8

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

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

Greg Wooledge มีหลายสิ่งที่ควรพูดเกี่ยวกับอันตรายของset -e:

ในการเชื่อมโยงที่สองมีตัวอย่างต่างๆของพฤติกรรม unintuitive set -eและคาดเดาไม่ได้ของ

ตัวอย่างบางส่วนของพฤติกรรมที่ไม่เข้าใจง่ายของset -e(บางคนนำมาจากลิงก์วิกิข้างต้น):

  • ชุด -e
    x = 0
    ให้ x ++
    echo "x คือ $ x"
    ด้านบนจะทำให้เชลล์สคริปต์ออกก่อนกำหนดเนื่องจากlet x++ส่งคืน 0 ซึ่งถือว่าโดยletคำหลักเป็นค่าที่ผิดพลาดและเปลี่ยนเป็นรหัสออกที่ไม่ใช่ศูนย์ set -eสังเกตสิ่งนี้และยุติสคริปต์แบบเงียบ ๆ
  • ชุด -e
    [-d / opt / foo] && echo "คำเตือน: ติดตั้ง foo แล้วจะเขียนทับ" > & 2
    echo "กำลังติดตั้ง foo ... "
    

    งานด้านบนตามที่คาดไว้พิมพ์คำเตือนหาก/opt/fooมีอยู่แล้ว

    ชุด -e
    check_previous_install () {
        [-d / opt / foo] && echo "คำเตือน: ติดตั้ง foo แล้วจะเขียนทับ" > & 2
    }
    
    check_previous_install
    echo "กำลังติดตั้ง foo ... "
    

    ด้านบนแม้จะมีความแตกต่างเพียงอย่างเดียวที่บรรทัดเดียวได้รับการปรับโครงสร้างให้เป็นฟังก์ชัน แต่จะยุติหาก/opt/fooไม่มีอยู่ เนื่องจากความจริงที่ว่ามันทำงานได้ แต่เดิมนั้นเป็นข้อยกเว้นพิเศษสำหรับset -eพฤติกรรมของ เมื่อผลตอบแทนที่ไม่ใช่ศูนย์ก็จะถูกละเลยโดยa && b set -eอย่างไรก็ตามตอนนี้มันเป็นฟังก์ชั่นรหัสออกของฟังก์ชั่นจะเท่ากับรหัสทางออกของคำสั่งนั้นและฟังก์ชั่นที่ส่งกลับไม่ใช่ศูนย์จะยุติสคริปต์อย่างเงียบ ๆ

  • ชุด -e
    IFS = $ '\ n' read -d '' -r -a config_vars <config
    

    ดังกล่าวข้างต้นจะอ่านอาร์เรย์จากแฟ้มconfig_vars configในฐานะผู้เขียนอาจตั้งใจมันจะจบลงด้วยข้อผิดพลาดหากconfigหายไป เนื่องจากผู้เขียนอาจไม่ได้ตั้งใจมันจะเงียบหากconfigไม่ได้ขึ้นบรรทัดใหม่ หากset -eไม่ได้ใช้ที่นี่config_varsจะมีทุกบรรทัดของไฟล์ไม่ว่าจะสิ้นสุดในการขึ้นบรรทัดใหม่หรือไม่

    ผู้ใช้ Sublime Text (และโปรแกรมแก้ไขข้อความอื่นที่จัดการบรรทัดใหม่ไม่ถูกต้อง) ระวัง

  • ชุด -e
    
    should_audit_user () {
        กลุ่มโลคัล = "$ (กลุ่ม" $ 1 ")"
        สำหรับกลุ่มในกลุ่ม $; ทำ
            หาก ["$ group" = audit]; จากนั้นส่งคืน 0 Fi
        เสร็จแล้ว
        คืน 1
    }
    
    ถ้า should_audit_user "$ user"; แล้วก็
        คนตัดไม้ 'Blah'
    Fi
    

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

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

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

ข้อเสียเพิ่มเติมของset -e:

  • มันส่งเสริมให้รหัสเลอะเทอะ ตัวจัดการข้อผิดพลาดจะถูกลืมอย่างสมบูรณ์โดยหวังว่าสิ่งใดก็ตามที่ล้มเหลวจะรายงานข้อผิดพลาดด้วยวิธีที่เหมาะสม อย่างไรก็ตามด้วยตัวอย่างlet x++ข้างต้นนี่ไม่ใช่กรณี หากสคริปต์ตายโดยไม่คาดคิดมักจะเงียบซึ่งเป็นอุปสรรคต่อการดีบัก หากสคริปต์ไม่ตายและคุณคาดหวังให้ (ดูหัวข้อย่อยก่อนหน้านี้) แสดงว่าคุณมีข้อบกพร่องที่บอบบางและร้ายกาจมากกว่าในมือของคุณ
  • มันนำผู้คนไปสู่ความปลอดภัยที่ผิดพลาด ดูifจุดกระสุน -condition อีกครั้ง
  • ตำแหน่งที่เชลล์หยุดทำงานไม่สอดคล้องกันระหว่างเชลล์หรือเวอร์ชันของเชลล์ เป็นไปได้ที่จะเขียนสคริปต์โดยไม่ตั้งใจซึ่งมีพฤติกรรมแตกต่างกันไปในเวอร์ชันเก่าของทุบตีเนื่องจากพฤติกรรมset -eการถูกปรับแต่งระหว่างเวอร์ชั่นเหล่านั้น

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

set -e ไม่ทดแทนการศึกษา


2

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


เห็นด้วยอย่างยิ่งกับคำตอบนี้ มันไม่ได้เป็นอันตรายโดยเนื้อแท้แต่มันเป็นโอกาสที่จะมีทางออกก่อนที่ไม่คาดคิด
pboin

1
สิ่งนี้ทำให้ฉันนึกถึงว่าเป็นศาสตราจารย์ C ++ ของฉันที่ดึงคะแนนจากงานที่มอบหมายของฉันเสมอโดยไม่ต้องมีจุดเข้า / ออกเดียวในโปรแกรม ฉันคิดว่ามันเป็น 'หลักการ' ล้วนๆ แต่สิ่งที่ธุรกิจชุดนี้สาธิตให้เห็นอย่างชัดเจนว่าทำไมและทำไมสิ่งต่าง ๆ จึงไม่สามารถควบคุมได้อย่างสมบูรณ์ ในที่สุดโปรแกรมที่เกี่ยวกับการควบคุมและการกำหนดและด้วยการยกเลิกก่อนกำหนดคุณให้ทั้งสอง
Marcin

0

เป็นตัวอย่างที่เป็นรูปธรรมมากขึ้นสำหรับคำตอบของ @ Marcin ที่กัดฉันเป็นการส่วนตัวลองจินตนาการว่ามีrm foo/bar.txtบรรทัดหนึ่งในสคริปต์ของคุณ มักจะไม่มีเรื่องใหญ่หากfoo/bar.txtไม่มีอยู่จริง อย่างไรก็ตามด้วยset -eปัจจุบันสคริปต์ของคุณจะสิ้นสุดก่อนเวลานั้น! อุ่ย

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