ความสำคัญของตัวดำเนินการเชิงตรรกะของเชลล์ &&, ||


126

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

true || echo aaa && echo bbb

อย่างไรก็ตามตรงกันข้ามกับความคาดหวังของฉันbbbได้รับการพิมพ์

ใครช่วยอธิบายหน่อยได้ไหมฉันจะทำให้รู้สึกถึงการทบต้น&&และ||ผู้ปฏิบัติงานในการทุบตีได้อย่างไร?

คำตอบ:


127

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

สิ่งนี้มีความสำคัญเพราะใน Bash &&และ||มีความสำคัญเหมือนกัน

ดังนั้นสิ่งที่เกิดขึ้นในตัวอย่างของคุณคือการดำเนินการทางซ้ายสุด ( ||) ดำเนินการก่อน:

true || echo aaa

เนื่องจากtrueเป็นความจริงอย่างชัดเจน||ผู้ดำเนินการลัดวงจรและข้อความทั้งหมดถือว่าเป็นจริงโดยไม่จำเป็นต้องประเมินecho aaaตามที่คุณคาดหวัง ตอนนี้มันยังคงทำการดำเนินการที่เหมาะสมที่สุด:

(...) && echo bbb

ตั้งแต่การดำเนินการครั้งแรกประเมินเป็นจริง (เช่นมีสถานะเป็น 0 ออก) มันเหมือนกับว่าคุณกำลังดำเนินการ

true && echo bbb

ดังนั้นความ&&ประสงค์จะไม่ลัดวงจรซึ่งเป็นเหตุผลที่คุณเห็นbbbสะท้อน

คุณจะได้รับพฤติกรรมเดียวกันกับ

false && echo aaa || echo bbb

หมายเหตุตามความคิดเห็น

  • คุณควรทราบว่ากฎซ้าย associativity เป็นเพียงปฏิบัติตามเมื่อผู้ประกอบการทั้งสองมีเหมือนกันมีความสำคัญ กรณีนี้ไม่ได้เมื่อคุณใช้ประกอบการเหล่านี้ร่วมกับคำหลักเช่น[[...]]หรือ((...))หรือใช้-oและ-aผู้ประกอบการเป็นข้อโต้แย้งไปtestหรือ[คำสั่ง ในกรณีเช่นนี้ AND ( &&หรือ-a) มีความสำคัญมากกว่า OR ( ||หรือ-o) ขอบคุณที่ความคิดเห็นของ Stephane Chazelas สำหรับการชี้แจงประเด็นนี้
  • ดูเหมือนว่าในภาษา Cและ C &&จะมีลำดับความสำคัญสูงกว่า||ซึ่งอาจเป็นสาเหตุที่คุณคาดว่าโครงสร้างดั้งเดิมของคุณจะมีลักษณะเหมือน

    true || (echo aaa && echo bbb). 

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

  • อาจมีบางกรณีที่ทั้ง 3นิพจน์ได้รับการประเมิน หากคำสั่งแรกคืนสถานะการออกที่ไม่เป็นศูนย์คำสั่ง||จะไม่ลัดวงจรและดำเนินการคำสั่งที่สอง หากคำสั่งที่สองกลับมาพร้อมกับสถานะออกจากศูนย์แล้ว&&จะไม่ลัดวงจรเช่นกันและคำสั่งที่สามจะถูกดำเนินการ ขอบคุณที่คอมเมนต์ของ Ignacio Vazquez-Abrams


26
ข้อความย่อที่ต้องเพิ่มความสับสนเล็กน้อย: ในขณะที่ตัวดำเนินการ&&และ||เชลล์cmd1 && cmd2 || cmd3มีความสำคัญเท่ากัน&&ใน((...))และ[[...]]มีความสำคัญมากกว่า||( ((a || b && c))คือ((a || (b && c)))) กันไปสำหรับ-a/ -oในtest/ [และfindและ&/ ใน| expr
Stéphane Chazelas

16
ในภาษาที่เหมือน c &&มีความสำคัญมากกว่า||ดังนั้นพฤติกรรมที่คาดหวังของ OP จะเกิดขึ้น ผู้ใช้ที่ไม่ระวังที่ใช้ภาษาดังกล่าวอาจไม่ทราบว่าในการทุบตีพวกเขามีลำดับความสำคัญเท่ากันดังนั้นจึงอาจคุ้มค่าที่จะชี้ให้เห็นอย่างชัดเจนว่าพวกเขามีความสำคัญในการทุบตีเดียวกัน
เควิน

โปรดทราบว่ามีสถานการณ์ที่สามารถเรียกใช้คำสั่งทั้งสามได้เช่นถ้าคำสั่งกลางสามารถส่งคืนค่าจริงหรือเท็จ
Ignacio Vazquez-Abrams

@Kevin โปรดตรวจสอบคำตอบที่แก้ไขแล้ว
โจเซฟอาร์

1
ในวันนี้สำหรับฉันแล้วสิ่งที่ Google ได้รับความนิยมสูงสุดสำหรับ "ลำดับความสำคัญของผู้ดำเนินการทุบตี" คือtldp.org/LDP/abs/html/opprecedence.html ... ซึ่งอ้างว่า && มีความสำคัญมากกว่า || แต่ @JosephR เห็นได้ชัดว่าเป็นพฤตินัยและทางนิตินัยเช่นกัน Dash ทำงานเหมือนกันและค้นหา "ลำดับความสำคัญ" ในpubs.opengroup.org/onlinepubs/009695399/utilities/ …ฉันเห็นว่ามันเป็นข้อกำหนดของ POSIX ดังนั้นเราจึงสามารถพึ่งพาได้ สิ่งที่ฉันพบสำหรับการรายงานข้อผิดพลาดคือที่อยู่อีเมลของผู้เขียนซึ่งแน่นอนว่าได้ถูกส่งสแปมถึงตายในช่วงหกปีที่ผ่านมา ฉันจะลองต่อไป ...
Martin Dorey

62

หากคุณต้องการให้หลายสิ่งขึ้นอยู่กับสภาพของคุณให้จัดกลุ่มสิ่งเหล่านี้:

true || { echo aaa && echo bbb; }

ที่พิมพ์อะไรในขณะที่

true && { echo aaa && echo bbb; }

พิมพ์ทั้งสองสตริง


เหตุผลนี้เกิดขึ้นง่ายกว่าโจเซฟกำลังทำอยู่ โปรดจำไว้ว่าสิ่งที่ทุบตีจะมีและ|| &&มันคือทั้งหมดที่เกี่ยวกับสถานะการส่งคืนคำสั่งก่อนหน้า วิธีที่แท้จริงในการดูคำสั่ง raw ของคุณคือ:

( true || echo aaa ) && echo bbb

คำสั่งแรก ( true || echo aaa) 0จะออกด้วย

$ true || echo aaa; echo $?
0
$ true && echo aaa; echo $?
aaa
0

$ false && echo aaa; echo $?
1
$ false || echo aaa; echo $?
aaa
0

7
+1 สำหรับการบรรลุผลลัพธ์ที่ต้องการของ OP โปรดทราบว่าวงเล็บที่คุณใส่เข้าไป(true || echo aaa) && echo bbbนั้นเป็นสิ่งที่ฉันทำ
โจเซฟอาร์

1
ยินดีที่ได้เห็น @Oli ในด้านนี้ของโลก
Braiam

7
สังเกตคอลัมน์กึ่ง;ท้ายสุด หากปราศจากสิ่งนี้มันจะไม่ทำงาน!
Serge Stroobandt

2
ฉันกำลังมีปัญหากับเรื่องนี้เพราะฉันขาดเซมิโคลอนต่อท้ายหลังจากคำสั่งสุดท้าย (เช่นecho bbb;ในตัวอย่างแรก) เมื่อฉันรู้ว่าฉันหายไปคำตอบนี้เป็นสิ่งที่ฉันกำลังมองหา +1 ที่ช่วยฉันหาวิธีทำให้สำเร็จตามที่ฉันต้องการ!
Doktor J

5
น่าอ่านสำหรับทุกคนสับสน(...)และ{...}สัญกรณ์: คู่มือการจัดกลุ่มคำสั่ง
ANTARA

16

ตัวดำเนินการ&&และ||ตัวดำเนินการไม่ใช่การแทนที่แบบอินไลน์ที่แน่นอนสำหรับ if-then-else แม้ว่าจะใช้อย่างระมัดระวัง แต่ก็สามารถทำสิ่งเดียวกันให้สำเร็จได้

การทดสอบเพียงครั้งเดียวนั้นตรงไปตรงมาและชัดเจน ...

[[ A == A ]]  && echo TRUE                          # TRUE
[[ A == B ]]  && echo TRUE                          # 
[[ A == A ]]  || echo FALSE                         # 
[[ A == B ]]  || echo FALSE                         # FALSE

อย่างไรก็ตามการพยายามเพิ่มการทดสอบหลายครั้งอาจให้ผลลัพธ์ที่ไม่คาดคิด ...

[[ A == A ]]  && echo TRUE   || echo FALSE          # TRUE  (as expected)
[[ A == B ]]  && echo TRUE   || echo FALSE          # FALSE (as expected)
[[ A == A ]]  || echo FALSE  && echo TRUE           # TRUE  (as expected)
[[ A == B ]]  || echo FALSE  && echo TRUE           # FALSE TRUE   (huh?)

ทำไมทั้ง FALSE และ TRUE จึงถูกสะท้อน

สิ่งที่เกิดขึ้นที่นี่คือเราไม่ได้ตระหนัก&&และ||มีตัวดำเนินการโอเวอร์โหลดที่ทำหน้าที่แตกต่างกันในวงเล็บทดสอบตามเงื่อนไข[[ ]]มากกว่าที่พวกเขาทำในรายการ AND และ OR (ดำเนินการตามเงื่อนไข) ที่เรามีที่นี่

จาก manpage ทุบตี (แก้ไข) ...

รายการ

รายการคือลำดับของหนึ่งหรือมากกว่าหนึ่งท่อคั่นด้วยหนึ่งในผู้ประกอบการ; &, & &, หรือ││และยกเลิกทางเลือกโดยหนึ่งใน; &, หรือ ของโอเปอเรเตอร์รายการเหล่านี้ && และ││มีลำดับความสำคัญเท่ากันตามด้วย; และ & ซึ่งมีความสำคัญเท่ากัน

ลำดับของการขึ้นบรรทัดใหม่อย่างน้อยหนึ่งรายการอาจปรากฏในรายการแทนที่จะใช้เครื่องหมายอัฒภาคเพื่อคั่นคำสั่ง

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

รายการ AND และ OR เป็นลำดับของหนึ่งในท่อที่คั่นด้วยตัวดำเนินการควบคุม && และ││ตามลำดับ รายการ AND และ OR ถูกดำเนินการด้วยการเชื่อมโยงด้านซ้าย

รายการ AND มีรูปแบบ ...
command1 && command2
Command2 ถูกดำเนินการถ้าและถ้า command1 ส่งคืนสถานะการออกเป็นศูนย์

รายการ OR มีรูปแบบ ...
command1 ││ command2
Command2 จะถูกดำเนินการถ้าหาก command1 ส่งคืนสถานะการออกที่ไม่ใช่ศูนย์

สถานะการส่งคืนของรายการ AND และ OR คือสถานะการออกของคำสั่งสุดท้ายที่ดำเนินการในรายการ

กลับไปเป็นตัวอย่างสุดท้ายของเรา ...

[[ A == B ]]  || echo FALSE  && echo TRUE

[[ A == B ]]  is false

     ||       Does NOT mean OR! It means...
              'execute next command if last command return code(rc) was false'

 echo FALSE   The 'echo' command rc is always true
              (i.e. it successfully echoed the word "FALSE")

     &&       Execute next command if last command rc was true

 echo TRUE    Since the 'echo FALSE' rc was true, then echo "TRUE"

ถูก หากถูกต้องแล้วทำไมตัวอย่างถัดไปสุดท้ายจึงสะท้อนสิ่งใดเลย

[[ A == A ]]  || echo FALSE  && echo TRUE


[[ A == A ]]  is true

     ||       execute next command if last command rc was false.

 echo FALSE   Since last rc was true, shouldn't it have stopped before this?
                Nope. Instead, it skips the 'echo FALSE', does not even try to
                execute it, and continues looking for a `&&` clause.

     &&       ... which it finds here

 echo TRUE    ... so, since `[[ A == A ]]` is true, then it echos "TRUE"

ความเสี่ยงของข้อผิดพลาดทางตรรกะเมื่อใช้มากกว่าหนึ่งรายการ&&หรือ||ในรายการคำสั่งนั้นค่อนข้างสูง

ข้อเสนอแนะ

รายการคำสั่งเดียว&&หรือ||ในรายการทำงานตามที่คาดไว้จึงค่อนข้างปลอดภัย หากเป็นสถานการณ์ที่คุณไม่จำเป็นต้องใช้ประโยคอื่น ๆ สิ่งต่อไปนี้สามารถทำให้ชัดเจนขึ้นเพื่อติดตาม (จำเป็นต้องใช้วงเล็บปีกกาเพื่อจัดกลุ่มคำสั่ง 2 รายการสุดท้าย) ...

[[ $1 == --help ]]  && { echo "$HELP"; exit; }

หลาย&&และ||ผู้ประกอบการที่แต่ละคำสั่งยกเว้นสำหรับที่ผ่านมาคือการทดสอบ (เช่นวงเล็บภายใน[[ ]]) มักจะยังปลอดภัยทั้งหมด แต่ประพฤติประกอบการที่ผ่านมาตามที่คาดไว้ ตัวดำเนินการสุดท้ายทำหน้าที่เหมือนthenหรือelseประโยคมากกว่า


0

ฉันสับสนด้วย แต่นี่เป็นวิธีที่ฉันคิดเกี่ยวกับวิธีที่ Bash อ่านข้อความของคุณ (เพราะอ่านสัญลักษณ์จากซ้ายไปขวา):

  1. trueสัญลักษณ์พบ สิ่งนี้จะต้องได้รับการประเมินเมื่อถึงจุดสิ้นสุดของคำสั่ง ณ จุดนี้ไม่ทราบว่ามันมีข้อโต้แย้งใด ๆ จัดเก็บคำสั่งในบัฟเฟอร์การดำเนินการ
  2. ||สัญลักษณ์พบ คำสั่งก่อนหน้านี้เสร็จสมบูรณ์แล้วดังนั้นประเมินมัน คำสั่ง (กันชน) trueถูกดำเนินการ: ผลการประเมิน: 0 (เช่นความสำเร็จ) เก็บผลลัพธ์ 0 ในการลงทะเบียน "การประเมินล่าสุด" ตอนนี้พิจารณาสัญลักษณ์ของ||ตัวเอง ขึ้นอยู่กับผลลัพธ์ของการประเมินครั้งล่าสุดว่าไม่ใช่ศูนย์ ทำเครื่องหมายที่ "การประเมินล่าสุด" ลงทะเบียนและพบ 0 เนื่องจาก 0 ไม่ได้เป็นศูนย์คำสั่งต่อไปนี้ไม่จำเป็นต้องได้รับการประเมิน
  3. echoสัญลักษณ์พบ สามารถละเว้นสัญลักษณ์นี้ได้เนื่องจากคำสั่งต่อไปนี้ไม่จำเป็นต้องได้รับการประเมิน
  4. aaaสัญลักษณ์พบ นี่คืออาร์กิวเมนต์ของคำสั่งecho(3) แต่เนื่องจากecho(3) ไม่จำเป็นต้องถูกประเมินจึงสามารถเพิกเฉยได้
  5. &&สัญลักษณ์พบ ขึ้นอยู่กับผลลัพธ์ของการประเมินล่าสุดที่เป็นศูนย์ ทำเครื่องหมายที่ "การประเมินล่าสุด" ลงทะเบียนและพบ 0 เนื่องจาก 0 เป็นศูนย์คำสั่งต่อไปนี้ไม่จำเป็นต้องได้รับการประเมิน
  6. echoสัญลักษณ์พบ คำสั่งนี้ต้องได้รับการประเมินเมื่อถึงจุดสิ้นสุดของคำสั่งเนื่องจากคำสั่งต่อไปนี้ไม่จำเป็นต้องได้รับการประเมิน จัดเก็บคำสั่งในบัฟเฟอร์การดำเนินการ
  7. bbbสัญลักษณ์พบ นี่คืออาร์กิวเมนต์ของคำสั่งecho(6) เนื่องจากechoจำเป็นต้องได้รับการประเมินให้เพิ่มbbbไปยังบัฟเฟอร์การดำเนินการ
  8. ถึงจุดสิ้นสุดของบรรทัดแล้ว คำสั่งก่อนหน้านี้เสร็จสมบูรณ์แล้วและไม่จำเป็นต้องได้รับการประเมิน คำสั่ง (กันชน) echo bbbถูกดำเนินการ: ผลการประเมิน: 0 (เช่นความสำเร็จ) เก็บผลลัพธ์ 0 ในการลงทะเบียน "การประเมินล่าสุด"

และแน่นอนขั้นตอนสุดท้ายทำให้bbbเสียงสะท้อนไปยังคอนโซล

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