Là nếu câu lệnh khác tương đương với logic và && hoặc || và tôi nên thích cái này hơn cái kia ở đâu?


27

Tôi đang tìm hiểu về các cấu trúc ra quyết định và tôi đã xem qua các mã này:

if [ -f ./myfile ]
then
     cat ./myfile
else
     cat /home/user/myfile
fi


[ -f ./myfile ] &&
cat ./myfile ||
cat /home/user/myfile

Cả hai đều cư xử giống nhau. Có bất kỳ lợi thế để sử dụng một cách từ cách khác?



3
Chúng không tương đương. Xem câu trả lời tuyệt vời của Icarus. Ví dụ, hãy xem xét trường hợp ./myfile tồn tại nhưng không thể đọc được.
AlexP


Câu trả lời:


26

Không, công trình xây dựng if A; then B; else C; fiA && B || Ckhông tương đương .

Với if A; then B; else C; fi, lệnh Aluôn được ước tính và thực thi (ít nhất là một nỗ lực để thực thi nó được thực hiện) và sau đó lệnh Bhoặc lệnh Cđược ước tính và thực thi.

Với A && B || C, nó giống cho các lệnh ABnhưng khác nhau cho C: lệnh Cđược đánh giá và thực hiện nếu một trong hai A thất bại hay B thất bại.

Trong ví dụ của bạn, giả sử bạn chmod u-r ./myfile, sau đó, mặc dù [ -f ./myfile ]thành công, bạn sẽcat /home/user/myfile

Lời khuyên của tôi: sử dụng A && Bhoặc A || Btất cả những gì bạn muốn, điều này vẫn dễ đọc và dễ hiểu và không có bẫy. Nhưng nếu bạn có nghĩa là nếu ... thì ... khác ... thì hãy sử dụng if A; then B; else C; fi.


29

Hầu hết mọi người tìm thấy nó dễ dàng hơn để hiểu được if... then... else... fihình thức.

Đối với a && b || c, bạn phải chắc chắn rằng btrả về đúng sự thật. Đây là một nguyên nhân của lỗi tinh tế và là một lý do tốt để tránh phong cách này. Nếu b không trả về đúng thì đây không giống nhau.

 $ if true; then false ; else echo boom ; fi
 $ true && false || echo boom
 boom

Đối với các thử nghiệm và hành động rất ngắn không có mệnh đề khác, độ dài rút ngắn rất hấp dẫn, ví dụ:

 die(){ printf "%s: %s\n" "$0" "$*" >&2 ; exit 1; }

 [ "$#" -eq 2] || die "Needs 2 arguments, input and output"

 if [ "$#" -ne 2 ] ; then
     die "Needs 2 arguments, input and output"
 fi

&&||short circuiting operators, càng sớm càng kết quả được biết đến xét nghiệm không cần thiết hơn nữa là bỏ qua. a && b || cđược nhóm lại thành (a && b) || c. Đầu tiên alà chạy. Nếu nó failsđược xác định là không trả về trạng thái thoát là 0, thì nhóm (a && b)được biết đến failbkhông cần phải chạy. Các ||không biết kết quả của biểu thức nên cần phải thực hiện c. Nếu athành công (trả về 0) thì &&toán tử chưa biết kết quả của việc a && bđó phải chạy bđể tìm hiểu. Nếu bthành công thì a && bthành công và ||biết kết quả chung là thành công, vì vậy không cần phải chạy c. Nếu bthất bại thì||vẫn không biết giá trị của biểu thức, do đó không cần phải chạy c.


7

Toán tử && thực thi lệnh tiếp theo nếu lệnh trước đó đã thực thi thành công, (mã thoát được trả về ($?) 0 = logic true).

Ở dạng A && B || C, lệnh (hoặc điều kiện) A được ước tính và nếu A trả về true (thành công, mã thoát 0) thì lệnh B được thực thi. Nếu A thất bại (do đó sẽ trả về false - mã thoát khác 0) và / hoặc B không thành công (trả về false ) thì lệnh C sẽ được thực thi.

Ngoài ra &&toán tử được sử dụng như một AND trong kiểm tra điều kiện và toán tử ||hoạt động như OR trong kiểm tra điều kiện.

Tùy thuộc vào những gì bạn muốn làm với tập lệnh của mình, biểu mẫu A && B || Ccó thể được sử dụng để kiểm tra điều kiện như ví dụ của bạn hoặc có thể được sử dụng để xâu chuỗi các lệnh và đảm bảo một loạt các lệnh sẽ được thực thi nếu các lệnh trước đó có mã thoát 0 thành công .
Đây là lý do tại sao thường thấy các lệnh như :
do_something && do_something_else_that_depended_on_something.

Ví dụ:
apt-get update && apt-get upgrade Nếu cập nhật thất bại thì việc nâng cấp không được thực thi, (có ý nghĩa trong thế giới thực ...).

mkdir test && echo "Something" > test/file
Phần echo "Something"này sẽ chỉ được thực hiện nếu mkdir testthành công và thao tác trả về mã thoát 0 .

./configure --prefix=/usr && make && sudo make install
Thường được tìm thấy trên các công việc biên dịch để xâu chuỗi các lệnh phụ thuộc cần thiết lại với nhau.

Nếu bạn cố gắng thực hiện các "chuỗi" ở trên với if - thì - nếu không, bạn sẽ cần nhiều lệnh và kiểm tra hơn (và do đó sẽ có nhiều mã hơn để viết - nhiều lỗi hơn) cho một nhiệm vụ đơn giản.

Ngoài ra, hãy nhớ rằng các chuỗi được xâu chuỗi với &&|| được đọc bằng vỏ trái sang phải. Bạn có thể cần nhóm các lệnh và kiểm tra điều kiện với dấu ngoặc để phụ thuộc vào bước tiếp theo vào đầu ra thành công của một số lệnh trước đó. Ví dụ xem điều này:

root@debian:$ true || true && false;echo $?
1 
#read from left to right
#true OR true=true AND false = false = exit code 1=not success

root@debian:$ true || (true && false);echo $?
0 
# true OR (true AND false)=true OR false = true = exit code 0 = success

Hoặc một ví dụ thực tế:

root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 ]] && [[ $c -eq 2 ]];echo $?
1 
#condition $a = true OR condition b = true AND condition $c = false
#=> yields false as read from left to right, thus exit code=1 = not ok

root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 && $c -eq 2 ]];echo $?
0 
#vars b and c are checked in a group which returns false, 
#condition check of var a returns true, thus true OR false yields true = exit code 0

Hãy nhớ rằng một số lệnh trả về các mã thoát khác nhau tùy thuộc vào quá trình được thực thi hoặc trả về các mã khác nhau tùy thuộc vào hành động của chúng, (ví dụ lệnh GNU diff, trả về 1 nếu hai tệp khác nhau và 0 nếu không). Các lệnh như vậy cần được xử lý cẩn thận trong &&|| .

Cũng chỉ để có tất cả các câu đố với nhau, hãy nhớ đến việc ghép các lệnh bằng ;toán tử. Với một định dạng, A;B;Ctất cả các lệnh sẽ được thực thi theo chuỗi bất kể mã thoát lệnh là gì AB.


1

Phần lớn sự nhầm lẫn về điều này có thể là do tài liệu bash gọi các danh sách AND và OR này . Mặc dù về mặt logic tương tự như &&||được tìm thấy bên trong dấu ngoặc vuông, chúng hoạt động khác nhau.

Một số ví dụ có thể minh họa điều này tốt nhất ...

GHI CHÚ: Dấu ngoặc vuông đơn và đôi ( [ ... ][[ ... ]]) là các lệnh theo quyền riêng của chúng để so sánh và trả về mã thoát. Họ không thực sự cần if.

cmda  && cmdb  || cmdc

Nếu cmdathoát đúng, cmdbđược thực thi.
Nếu cmdathoát sai, cmdbKHÔNG được thực thi, nhưng cmdclà.

cmda; cmdb  && cmdc  || cmdd

Làm thế nào cmdathoát được bỏ qua.
Nếu cmdbthoát đúng, cmdcđược thực thi.
Nếu cmdbthoát sai, cmdcKHÔNG được thực thi và cmddlà.

cmda  && cmdb; cmdc

Nếu cmdathoát đúng, cmdbđược thực thi, theo sau cmdc.
Nếu cmdathoát sai, cmdbKHÔNG được thực thi nhưng cmdclà.

Huh? Tại sao cmdcbị xử tử?
Bởi vì đối với người phiên dịch, một dấu chấm phẩy ( ;) và một dòng mới có nghĩa chính xác là cùng một điều. Bash thấy dòng mã đó là ...

cmda  && cmdb
cmdc  

Để đạt được những gì được mong đợi, chúng ta phải đặt cmdb; cmdcbên trong các dấu ngoặc nhọn để biến chúng thành Lệnh hợp chất (lệnh nhóm) . Dấu chấm phẩy kết thúc bổ sung chỉ là một yêu cầu của { ...; }cú pháp. Vì vậy, chúng tôi nhận được ...

cmda && { cmdb; cmdc; }
Nếu cmdathoát đúng, cmdbđược thực thi, theo sau cmdc.
Nếu cmdathoát sai, không cmdbhoặc cmdcđược thực thi.
Thi hành tiếp tục với dòng tiếp theo.

Sử dụng

Danh sách lệnh có điều kiện là hữu ích nhất để trả về càng sớm càng tốt từ các hàm và do đó tránh việc diễn giải và thực thi nhiều mã không cần thiết. Tuy nhiên, nhiều hàm trả về có nghĩa là người ta phải bị ám ảnh về việc giữ các hàm ngắn để dễ bảo đảm rằng tất cả các điều kiện có thể được bảo hiểm.

Đây là một ví dụ từ một số mã đang chạy ...

fnInit () {
  :
  _fn="$1"
  ### fnInit "${FUNCNAME}" ...
  ### first argument MUST be name of the calling function
  #
  [[ "$2" == "--help-all" ]]  && { helpAll                      ; return 0; }
  ### pick from list of functions
  #
  [[ "$2" == "--note-all" ]]  && { noteAll                      ; return 0; }
  ### pick from notes in METAFILE
  #
  [[ "$2" == "--version"  ]]  && { versionShow "${_fn}" "${@:3}"; return 0; }
  #
  [[ "$2" == "--function" ]]  && {
    isFnLoaded "$3"           && { "${@:3}"                     ; return 0; }
    #
    errorShow functionnotfound "Unknown function:  $3"
    return 0
  }
  ### call any loaded function
  #
  [[ "$2" == "--help" || "$2" == "-h" ]]  && { noteShow "$_fn" "${@:3}"; return 0; }
  ### fnInit "${FUNCNAME}" --help or -h
  #
  return 1
}
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.