Là thử nghiệm hoặc [hoặc [[di động hơn cả giữa vỏ bash và giữa các vỏ khác?


44

Tôi thấy tôi có thể làm

$ [ -w /home/durrantm ] && echo "writable"
writable

hoặc là

$ test -w /home/durrantm && echo "writable"
writable

hoặc là

$ [[ -w /home/durrantm ]] && echo "writable"
writable

Tôi thích sử dụng cú pháp thứ ba. Chúng có tương đương trong tất cả các cách và cho tất cả các trường hợp tiêu cực và cạnh? Có sự khác biệt nào về tính di động, ví dụ giữa bash trên Ubuntu và OS X hoặc các phiên bản bash cũ hơn / mới hơn, ví dụ trước / sau 4.0 và cả hai đều mở rộng các biểu thức theo cùng một cách?


2
Đối [ … ]với [[ … ]]vs test …, có câu trả lời đầy đủ hơn trong câu hỏi chủ yếu trùng lặp này .
Gilles 'SO- ngừng trở nên xấu xa'

Đối với vấn đề cụ thể về kiểm tra khả năng ghi của tệp, xem thêm Cách kiểm tra không xâm lấn để truy cập ghi vào tệp?
G-Man nói 'Phục hồi Monica'

1
Có một câu nói: "không có mã di động, chỉ có mã đã được chuyển". Lời khuyên của tôi liên quan đến vấn đề này: Sử dụng hình thức dễ đọc nhất (có thể là [[...]]) và thử nó trên tất cả các nền tảng bạn muốn hỗ trợ. Không có nhiều sử dụng trong việc che khuất các tập lệnh của bạn để chúng chạy trên các nền tảng cổ mà cả bạn và đối tượng mục tiêu của bạn không sử dụng. Nó sẽ chỉ làm cho mã của bạn khó đọc, giới thiệu các lỗi không cần thiết và thậm chí có thể là các vấn đề bảo mật (giống như đã xảy ra đối với openssl).
stefan.schwetschke

Câu trả lời:


31

[là từ đồng nghĩa của testlệnh và nó đồng thời là lệnh bash dựng sẵn và tách biệt. Nhưng [[là một từ khóa bash và chỉ hoạt động trong một số phiên bản. Vì vậy, vì lý do tính di động, bạn nên sử dụng một []hoặctest

[ -w "/home/durrantm" ] && echo "writable"

2
[POSIX dựng sẵn hay Bash dựng sẵn?
Sandburg

@Sandburg POSIX là một tiêu chuẩn, vì vậy nó không thể có sẵn. Nó không xác định nếu một cái gì đó nên được tích hợp vào trình thông dịch hay không, chỉ là những gì nó phải làm. Các quy tắc này áp dụng cho cả hai hình thức, test[như nhau. Nếu một shell có thể tự đánh giá, điều đó giúp bạn tiết kiệm một quy trình và làm cho nó nhanh hơn một chút, nhưng kết quả phải giống nhau.
Bachsau

@Bachsau vẫn không trả lời câu hỏi của Sandburg về việc liệu hành vi của [POSIX có được xác định hay không và do đó có thể mang theo tối đa (dường như là toàn bộ vấn đề của câu hỏi này nếu tôi không nhầm)
JamesTheAw đũaDude

@Sandburg (và cho bất kỳ ai khác vấp phải điều này): Vâng, có vẻ như cả hai [test là POSIX . Dường như không có "khuyến nghị" nào, mặc dù sự khác biệt duy nhất (?) Tôi có thể nhận ra giữa chúng là test"sẽ không nhận ra đối số" - "[như một dấu phân cách chỉ ra sự kết thúc của các tùy chọn]" (? - ngụ ý rằng " - " được công nhận là kết thúc tranh luận [, dù sao tôi thực sự không thể đưa ra một trường hợp thử nghiệm tốt. Nhưng tôi lạc đề. Sử dụng cái mà bạn sẽ. IMO, [trông thanh lịch, nhưng testrõ ràng là một mệnh lệnh)
JamesTheAw đũaDude

35

Vâng, có sự khác biệt. Di động nhất là testhoặc [ ]. Đây là cả một phần của đặc tả POSIXtest .

Cấu if ... fitrúc cũng được xác định bởi POSIX và phải hoàn toàn di động.

Đây [[ ]]là một kshtính năng cũng có mặt trong một số phiên bản của bash( tất cả các phiên bản hiện đại ), trong zshvà có lẽ trong các phiên bản khác nhưng không có trong shhoặc dashcác loại vỏ đơn giản khác.

Vì vậy, để làm cho tập lệnh của bạn di động, sử dụng [ ], testhoặc if ... fi.


10
Chỉ cần lưu ý, bashzshđã ủng hộ [[trong một thời gian rất dài ( bashthêm nó vào cuối những năm 90, zshchậm nhất là năm 2000, và tôi sẽ ngạc nhiên nếu nó bao giờ thiếu sự ủng hộ), vì vậy bạn không có khả năng gặp phải một phiên bản của một trong hai không [[. Gặp phải một vỏ tương thích POSIX khác (chẳng hạn như dash) có nhiều khả năng.
chepner

1
Một tính năng thú vị của [[việc mở rộng tham số không phải trích dẫn: [[-f $file]]sự kiện hoạt động nếu $filecó chứa các ký tự khoảng trắng.
helpermethod

2
@rcpermethod cũng vậy, biểu thức chính quy dựng sẵn[[ $a =~ ^reg.*exp.*$' ]]
GnP

20

Xin lưu ý, đó [] && cmdkhông giống như if .. fixây dựng.

Đôi khi hành vi của nó khá giống nhau và bạn có thể sử dụng [] && cmdthay vì if .. fi. Nhưng chỉ đôi khi. Nếu bạn có nhiều hơn thì một lệnh để thực thi nếu có điều kiện hoặc bạn cần if .. else .. ficẩn thận và xử lý logic.

Một vài ví dụ:

[ -z "$VAR" ] && ls file || echo wiiii

Không giống như

if [ -z $VAR ] ; then
  ls file
else
  echo wiii
fi

bởi vì nếu lsthất bại, echosẽ được thực thi sẽ không xảy ra với if.

Một vi dụ khac:

[ -z "$VAR" ] && ls file && echo wiii

không giống như

if [ -z "$VAR" ] ; then
   ls file
   echo $wiii
fi

mặc dù công trình này sẽ hoạt động như nhau

[ -z "$VAR" ] && { ls file ; echo wiii ; }

xin lưu ý ;sau khi tiếng vang là quan trọng và phải ở đó.

Vì vậy, tiếp tục tuyên bố trên chúng ta có thể nói

[] && cmd == nếu lệnh đầu tiên thành công thì thực hiện lệnh tiếp theo

if .. fi == nếu điều kiện (cũng có thể là lệnh kiểm tra) thì thực thi lệnh (s)

Vì vậy, cho tính di động giữa [và chỉ [[sử dụng [.

iftương thích POSIX. Vì vậy, nếu bạn phải lựa chọn giữa [ifchọn nhìn vào nhiệm vụ của bạn và hành vi dự kiến.


Có lẽ tôi chỉ dày đặc, nhưng tôi không thấy sự khác biệt sẽ là gì ... bạn có thể vui lòng cho một ví dụ về việc hai công trình đó sẽ cho kết quả khác nhau không?
evilsoup

1
@evilsoup, đã cập nhật. Có thể tôi không phải là người giải thích tốt nhất, mặc dù tôi hy vọng nó sẽ rõ ràng ngay bây giờ.
vội vàng

1
Điểm rất tốt, vội vàng. Tôi cho rằng một hình thức an toàn của ví dụ đầu tiên của bạn sẽ là : [ -z "$VAR" ] && { ls file; true; } || echo wiiii. Nó dài hơn một chút, nhưng nó vẫn ngắn hơn so với việc if...fixây dựng.
PM 2Ring

Đặt câu hỏi là về [vs [[vs test và không phải về ... fi vs && để biến nó thành MỘT câu hỏi. Thật không may làm điều đó làm cho câu trả lời này có vẻ lạc quan. Xin lỗi vì không nhận được câu hỏi m quặng tập trung ban đầu dẫn đến điều này. Sống và (cố gắng) học hỏi. :)
Michael Durrant

Tôi đánh giá thấp bởi vì a) đây chủ yếu là một câu trả lời tốt, nhưng nó là một câu trả lời cho câu hỏi khác nhau. b) bạn trình bày [iflàm phụ đề, nhưng họ không. Đó thực sự là &&cái thay thế if. [thực thi một số mã và trả về một trạng thái, rất giống lsgrepsẽ. ifthực thi các nhánh tùy thuộc vào trạng thái trả về của lệnh (câu lệnh) được đưa ra sau if, nó có thể là bất kỳ lệnh (câu lệnh) nào. &&thực hiện câu lệnh tiếp theo chỉ khi câu lệnh trước trả về 0, giống như một đơn giản if..then..fi.
GnP

9

Đó thực sự là &&cái thay thế if, chứ không phải test: một ifcâu lệnh trong shell script kiểm tra xem một lệnh có trả về trạng thái thoát "thành công" (không) hay không; trong ví dụ của bạn, lệnh là [.

Vì vậy, thực sự có hai điều bạn đang thay đổi ở đây: lệnh được sử dụng để chạy thử nghiệm và cú pháp được sử dụng để thực thi mã dựa trên kết quả của thử nghiệm đó.

Các lệnh kiểm tra:

  • testmột lệnh được tiêu chuẩn hóa để đánh giá các thuộc tính của chuỗi và tệp; trong ví dụ của bạn, bạn đang chạy lệnhtest -w /home/durrantm
  • [là một bí danh của lệnh đó, được tiêu chuẩn hóa bằng nhau, có một đối số cuối cùng bắt buộc ]để trông giống như một biểu thức được đặt trong ngoặc; đừng để bị lừa, nó vẫn chỉ là một lệnh (thậm chí bạn có thể thấy rằng hệ thống của bạn có một tệp được gọi là /bin/[)
  • [[là phiên bản mở rộng của lệnh thử nghiệm được tích hợp trong một số shell, nhưng không phải là một phần của cùng tiêu chuẩn POSIX; nó bao gồm các tùy chọn bổ sung mà bạn không sử dụng ở đây

Biểu thức điều kiện:

  • Các &&nhà điều hành ( chuẩn ở đây ) thực hiện một logic AND hoạt động, bằng cách đánh giá hai lệnh và trở về 0 (mà đại diện true) nếu cả hai đều return 0; nó sẽ chỉ đánh giá lệnh thứ hai nếu lệnh thứ nhất trả về 0, vì vậy nó có thể được sử dụng như một điều kiện đơn giản
  • Cấu if ... then ... fitrúc (được chuẩn hóa ở đây ) sử dụng cùng một phương pháp đánh giá "sự thật", nhưng cho phép một danh sách tổng hợp các câu trong thenmệnh đề, thay vì một lệnh duy nhất được cung cấp bởi một &&mạch ngắn, và cung cấp elifelsemệnh đề, khó viết chỉ sử dụng &&||. Lưu ý rằng không có dấu ngoặc quanh điều kiện trong một ifcâu lệnh .

Vì vậy, các kết xuất sau đây đều có tính di động như nhau và hoàn toàn tương đương với các ví dụ của bạn:

  • test -w /home/durrantm && echo "writable"
  • [ -w /home/durrantm ] && echo "writable"
  • if test -w /home/durrantm; then echo "writable"; fi
  • if [ -w /home/durrantm ]; then echo "writable"; fi

Mặc dù sau đây cũng tương đương, nhưng ít di động hơn do tính chất không chuẩn của [[:

  • [[ -w /home/durrantm ]] && echo "writable"
  • if [[ -w /home/durrantm ]]; then echo "writable"; fi

Có, tôi đã xóa phần if .... fi để thực hiện đây là 1 câu hỏi.
Michael Durrant

7

Đối với tính di động, sử dụng test/ [. Nhưng nếu bạn không cần tính di động, vì lợi ích của sự tỉnh táo của bạn và những người khác đọc kịch bản của bạn sử dụng [[. :)

Cũng xem What is the difference between test, [ and [[ ?trong BashFAQ .


7

Nếu bạn muốn tính di động bên ngoài thế giới giống như Bourne, thì:

test -w /home/durrantm && echo writable

là di động nhất. Nó hoạt động trong vỏ của Bourne cshrcgia đình.

test -w /home/durrantm && echo "writable"

sẽ ra "writable"thay vì writabletrong vỏ của rcgia đình ( rc, es, akanga, nơi "không phải là đặc biệt).

[ -w /home/durrantm ] && echo writable

sẽ không hoạt động trong vỏ của gia đình cshhoặc rcgia đình trên các hệ thống không có [lệnh trong $PATH(một số được biết là có testnhưng không phải là [bí danh của nó ).

if [ -w /home/durrantm ]; then echo writabe; fi

chỉ hoạt động trong vỏ của gia đình Bourne.

[[ -w /home/durrantm ]] && echo writable

chỉ hoạt động ở ksh(nơi nó bắt nguồn) zshbash(cả 3 trong gia đình Bourne).

Không ai sẽ làm việc trong fishshell mà bạn cần:

[ -w /home/durrantm ]; and echo writable

hoặc là:

if [ -w /home/durrantm ]; echo writable; end

0

Lý do quan trọng nhất của tôi cho việc lựa chọn một trong hai if foo; then bar; fihoặc foo && barlà liệu các trạng thái thoát của toàn bộ lệnh là quan trọng.

so sánh:

#!/bin/sh
set -e
foo && bar
do_baz

với:

#!/bin/sh
set -e
if foo; then bar; fi
do_baz

Bạn có thể nghĩ rằng họ làm như vậy; tuy nhiên nếu foothất bại (hoặc sai, tùy theo quan điểm của bạn) thì trong ví dụ đầu tiên do_baz sẽ không được thực thi, vì tập lệnh sẽ thoát ... Lệnh này set -esẽ thoát ngay lập tức nếu bất kỳ lệnh nào trả về trạng thái sai. Rất hữu ích nếu bạn đang làm những việc như:

cd /some/directory
rm -rf *

Bạn không muốn tập lệnh tiếp tục chạy nếu cdthất bại vì bất kỳ lý do gì.


1
Không có một thất bại foosẽ không hủy bỏ tập lệnh trong cả hai trường hợp. Đó là trường hợp đặc biệt cho set -e(khi lệnh được đánh giá là điều kiện (ở bên trái của một số && / || hoặc trong điều kiện if / while / Until / elsif ...). Một lỗi barsẽ thoát khỏi trình bao trong cả hai trường hợp.
Stéphane Chazelas 17/11/14

2
Bạn muốn cd /some/directory && rm -rf -- *hoặc cd /some/directory || exit; rm -rf -- *(vẫn không xóa các tập tin ẩn). Cá nhân tôi không thích ý tưởng sử dụng set -enhư một cái cớ để không nỗ lực viết mã chính xác.
Stéphane Chazelas
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.