Ưu tiên của toán tử logic shell &&, ||


126

Tôi đang cố gắng hiểu làm thế nào các ưu tiên toán tử logic hoạt động trong bash. Ví dụ, tôi đã dự kiến ​​rằng lệnh sau không lặp lại bất cứ điều gì.

true || echo aaa && echo bbb

Tuy nhiên, trái với dự đoán của tôi, bbbđược in.

Ai đó có thể vui lòng giải thích, làm thế nào tôi có thể hiểu ý nghĩa của các toán tử &&||toán tử trong bash?

Câu trả lời:


127

Trong nhiều ngôn ngữ máy tính, các toán tử có cùng mức ưu tiên là liên kết trái . Đó là, trong trường hợp không có cấu trúc nhóm, các hoạt động ngoài cùng bên trái được thực hiện trước tiên. Bash cũng không ngoại lệ cho quy tắc này.

Điều này rất quan trọng bởi vì, trong Bash, &&||có cùng một ưu tiên.

Vì vậy, điều xảy ra trong ví dụ của bạn là thao tác ngoài cùng bên trái ( ||) được thực hiện trước:

true || echo aaa

truerõ ràng là đúng, ||toán tử ngắn mạch và toàn bộ câu lệnh được coi là đúng mà không cần phải đánh giá echo aaanhư bạn mong đợi. Bây giờ nó vẫn còn để thực hiện các hoạt động ngoài cùng bên phải:

(...) && echo bbb

Vì thao tác đầu tiên được đánh giá là đúng (nghĩa là có trạng thái thoát 0), nên như thể bạn đang thực hiện

true && echo bbb

Vì vậy, &&sẽ không ngắn mạch, đó là lý do tại sao bạn nhìn thấy tiếng bbbvang.

Bạn sẽ có hành vi tương tự với

false && echo aaa || echo bbb

Ghi chú dựa trên các ý kiến

  • Bạn nên lưu ý rằng quy tắc kết hợp trái chỉ được tuân theo khi cả hai toán tử có cùng mức ưu tiên. Đây không phải là trường hợp khi bạn sử dụng các toán tử này kết hợp với các từ khóa như [[...]]hoặc ((...))hoặc sử dụng các toán tử -o-atoán tử làm đối số cho các lệnh testhoặc [. Trong những trường hợp như vậy, AND ( &&hoặc -a) được ưu tiên hơn OR ( ||hoặc -o). Cảm ơn bình luận của Stephane Chazelas vì đã làm rõ điểm này.
  • Có vẻ như trong các ngôn ngữ giống như C và C &&có độ ưu tiên cao hơn ||có lẽ đó là lý do tại sao bạn mong đợi cấu trúc ban đầu của mình hoạt động như thế nào

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

    Đây không phải là trường hợp với Bash, tuy nhiên, trong đó cả hai toán tử đều có cùng mức ưu tiên, đó là lý do Bash phân tích biểu thức của bạn bằng cách sử dụng quy tắc kết hợp trái. Cảm ơn bình luận của Kevin đã đưa ra điều này.

  • Cũng có thể có trường hợp cả 3 biểu thức được ước tính. Nếu lệnh đầu tiên trả về một trạng thái thoát khác không, những ||sẽ không ngắn mạch và tiếp tục thực hiện lệnh thứ hai. Nếu lệnh thứ hai trở lại với trạng thái thoát bằng 0, thì lệnh &&sẽ không bị đoản mạch và lệnh thứ ba sẽ được thực thi. Cảm ơn bình luận của Ignacio Vazquez-Abrams đã đưa ra điều này.


26
Một lưu ý nhỏ để thêm một chút nhầm lẫn: trong khi các toán tử shell &&||shell cmd1 && cmd2 || cmd3có cùng mức ưu tiên, thì &&in ((...))[[...]]có quyền ưu tiên hơn ||( ((a || b && c))is ((a || (b && c)))). Tương tự với -a/ -otrong test/ [find&/ |trong expr.
Stéphane Chazelas

16
Trong các ngôn ngữ giống như c, &&có độ ưu tiên cao hơn ||, do đó, hành vi dự kiến ​​của OP sẽ xảy ra. Người dùng không mong muốn sử dụng các ngôn ngữ như vậy có thể không nhận ra rằng trong bash họ có cùng mức độ ưu tiên, do đó, có thể đáng để chỉ ra rõ ràng hơn rằng họ có cùng mức ưu tiên trong bash.
Kevin

Cũng lưu ý rằng có những tình huống mà cả ba lệnh có thể được chạy, tức là nếu lệnh giữa có thể trả về đúng hoặc sai.
Ignacio Vazquez-Abrams

@Kevin Vui lòng kiểm tra câu trả lời đã được chỉnh sửa.
Joseph R.

1
Hôm nay, đối với tôi, lượt truy cập hàng đầu của Google cho "ưu tiên toán tử bash" là tldp.org/LDP/abs/html/opprecedence.html ... tuyên bố rằng && có mức độ ưu tiên cao hơn so với ||. Chưa @JosephR. rõ ràng là đúng de facto và de jure quá. Dash hành xử giống nhau và tìm kiếm "quyền ưu tiên" trong pubs.opengroup.org/onlinepub/009695399/utilities/ , tôi thấy đó là một yêu cầu POSIX, vì vậy chúng tôi có thể phụ thuộc vào nó. Tất cả những gì tôi tìm thấy để báo cáo lỗi là địa chỉ email của tác giả, chắc chắn đã bị spam đến chết trong sáu năm qua. Dù sao tôi cũng sẽ thử ...
Martin Dorey

62

Nếu bạn muốn nhiều thứ phụ thuộc vào điều kiện của bạn, hãy nhóm chúng:

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

Mà không in gì cả, trong khi

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

in cả hai chuỗi.


Lý do điều này xảy ra đơn giản hơn rất nhiều so với Joseph đang đưa ra. Hãy nhớ những gì Bash làm với ||&&. Đó là tất cả về trạng thái trả về của lệnh trước đó. Một cách hiểu theo nghĩa đen của lệnh thô của bạn là:

( true || echo aaa ) && echo bbb

Lệnh đầu tiên ( true || echo aaa) đang thoát với 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 Để đạt được kết quả dự định của OP. Lưu ý rằng dấu ngoặc đơn bạn đặt (true || echo aaa) && echo bbbchính xác là những gì tôi đang tạo ra.
Joseph R.

1
Rất vui khi thấy @Oli ở bên này của thế giới.
Braiam

7
Lưu ý cột bán ;ở cuối. Không có cái này, nó sẽ không hoạt động!
Serge Stroobandt

2
Tôi đã gặp rắc rối với điều này bởi vì tôi đã bỏ lỡ dấu chấm phẩy sau lệnh cuối cùng (ví dụ echo bbb;trong các ví dụ đầu tiên). Khi tôi nhận ra mình đã bỏ lỡ điều đó, câu trả lời này chính xác là những gì tôi đang tìm kiếm. +1 để giúp tôi tìm ra cách thực hiện những gì tôi muốn!
Doktor J

5
Đáng đọc cho bất cứ ai nhầm lẫn (...){...}ký hiệu: hướng dẫn nhóm lệnh
ANTARA

16

Các toán tử &&||toán tử không phải là sự thay thế nội tuyến chính xác cho if-then-other. Mặc dù nếu được sử dụng cẩn thận, chúng có thể thực hiện được nhiều điều tương tự.

Một bài kiểm tra đơn giản và rõ ràng ...

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

Tuy nhiên, cố gắng thêm nhiều thử nghiệm có thể mang lại kết quả không mong muốn ...

[[ 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?)

Tại sao cả hai FALSE TRUE lặp lại?

Điều đang xảy ra ở đây là chúng tôi đã không nhận ra điều đó &&||là các toán tử quá tải hoạt động khác nhau trong các khung kiểm tra có điều kiện [[ ]]so với các hoạt động trong danh sách AND và OR (thực thi có điều kiện) mà chúng tôi có ở đây.

Từ trang bash (chỉnh sửa) ...

Danh sách

Danh sách là một chuỗi gồm một hoặc nhiều đường ống được phân tách bởi một trong các toán tử;, &, &&, hoặc và được kết thúc tùy ý bởi một trong ;, &, hoặc. Trong số các toán tử danh sách này, && và có quyền ưu tiên như nhau, theo sau là; và &, có quyền ưu tiên như nhau.

Một chuỗi gồm một hoặc nhiều dòng mới có thể xuất hiện trong danh sách thay vì dấu chấm phẩy để phân định các lệnh.

Nếu một lệnh bị chấm dứt bởi toán tử điều khiển &, shell sẽ thực thi lệnh trong nền trong một lớp con. Shell không chờ lệnh kết thúc và trạng thái trả về là 0. Các lệnh được phân tách bằng a; được thực hiện tuần tự; Shell chờ lần lượt từng lệnh kết thúc. Trạng thái trả về là trạng thái thoát của lệnh cuối cùng được thực thi.

Danh sách AND và OR là các chuỗi của một trong nhiều đường ống được phân tách bằng các toán tử điều khiển && và tương ứng. Danh sách AND và OR được thực hiện với tính kết hợp trái.

Danh sách AND có dạng ...
command1 && command2
Command2 được thực thi nếu và chỉ khi, lệnh1 trả về trạng thái thoát bằng không.

Danh sách OR có dạng ...
command1 ││ command2
Command2 được thực thi khi và chỉ khi lệnh1 trả về trạng thái thoát khác không.

Trạng thái trả về của danh sách AND và OR là trạng thái thoát của lệnh cuối cùng được thực thi trong danh sách.

Trở lại ví dụ cuối cùng của chúng tôi ...

[[ 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"

Được chứ. Nếu đó là chính xác, thì tại sao ví dụ tiếp theo lại lặp lại bất cứ điều gì?

[[ 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"

Nguy cơ lỗi logic khi sử dụng nhiều hơn một &&hoặc ||trong danh sách lệnh là khá cao.

khuyến nghị

Một &&hoặc ||một danh sách lệnh hoạt động như mong đợi vì vậy khá an toàn. Nếu đó là một tình huống mà bạn không cần một mệnh đề khác, một cái gì đó như sau có thể rõ ràng hơn để làm theo (các dấu ngoặc nhọn được yêu cầu để nhóm 2 lệnh cuối cùng) ...

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

Nhiều toán tử &&||toán tử, trong đó mỗi lệnh ngoại trừ lệnh cuối cùng là một thử nghiệm (tức là bên trong ngoặc [[ ]]), thường cũng an toàn vì tất cả trừ toán tử cuối cùng hoạt động như mong đợi. Toán tử cuối hoạt động giống như một mệnh đề thenhoặc else.


0

Tôi cũng bị lẫn lộn bởi điều này nhưng đây là cách tôi nghĩ về cách Bash đọc câu nói của bạn (vì nó đọc các ký hiệu từ trái sang phải):

  1. Tìm thấy biểu tượng true. Điều này sẽ cần phải được đánh giá sau khi kết thúc lệnh. Tại thời điểm này, không biết nếu nó có bất kỳ đối số. Lưu trữ lệnh trong bộ đệm thực thi.
  2. Tìm thấy biểu tượng ||. Lệnh trước đã hoàn tất, vì vậy hãy đánh giá nó. Lệnh (bộ đệm) đang được thực thi : true. Kết quả đánh giá: 0 (tức là thành công). Lưu trữ kết quả 0 trong đăng ký "đánh giá cuối cùng". Bây giờ hãy xem xét biểu tượng ||chính nó. Điều này phụ thuộc vào kết quả của đánh giá cuối cùng là khác không. Đã kiểm tra đăng ký "đánh giá cuối cùng" và tìm thấy 0. Vì 0 không phải khác không, nên không cần phải đánh giá lệnh sau.
  3. Tìm thấy biểu tượng echo. Có thể bỏ qua biểu tượng này, vì lệnh sau không cần đánh giá.
  4. Tìm thấy biểu tượng aaa. Đây là một đối số cho lệnh echo(3), nhưng vì echo(3) không cần đánh giá, nên có thể bỏ qua.
  5. Tìm thấy biểu tượng &&. Điều này phụ thuộc vào kết quả đánh giá cuối cùng bằng không. Đã kiểm tra đăng ký "đánh giá cuối cùng" và tìm thấy 0. Vì 0 là 0, nên lệnh sau cần được đánh giá.
  6. Tìm thấy biểu tượng echo. Lệnh này cần được đánh giá sau khi kết thúc lệnh, bởi vì lệnh sau cần phải được đánh giá. Lưu trữ lệnh trong bộ đệm thực thi.
  7. Tìm thấy biểu tượng bbb. Đây là một đối số để ra lệnh echo(6). Vì echocần phải được đánh giá, thêm bbbvào bộ đệm thực thi.
  8. Đã đến cuối dòng. Lệnh trước đã hoàn tất và cần phải được đánh giá. Lệnh (bộ đệm) đang được thực thi : echo bbb. Kết quả đánh giá: 0 (tức là thành công). Lưu trữ kết quả 0 trong đăng ký "đánh giá cuối cùng".

Và tất nhiên, bước cuối cùng gây ra tiếng bbbvang cho giao diện điều khiển.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.