Sự khác biệt giữa các toán tử Bash [[vs [vs (vs ((??


246

Tôi hơi bối rối về những gì các toán tử này làm khác nhau khi được sử dụng trong bash (ngoặc, ngoặc kép, ngoặc đơn và ngoặc kép).

[[ , [ , ( , ((

Tôi đã thấy mọi người sử dụng chúng nếu các câu như thế này:

if [[condition]]

if [condition]

if ((condition))

if (condition)

4
Bạn có thể muốn xem unix.stackexchange.com/questions/tagged/test trước
cuonglm


3
@cuonglm mỉa mai vì liên kết đó mang lại câu hỏi này là kết quả đầu tiên. Nghịch lý!
Mất trí

5
Tôi cho rằng đọc tài liệu không phải là một lựa chọn?
Các cuộc đua nhẹ nhàng trong quỹ đạo

34
Dấu ngoặc đơn và dấu ngoặc không dễ tìm kiếm trong tài liệu và đó là tất cả những gì bạn có nếu bạn không biết tên của các tính năng đó.
ilkkachu

Câu trả lời:


258

Một iftuyên bố thường trông giống như

if commands1
then
   commands2
else
   commands3
fi

Các thenkhoản được thực hiện nếu mã lối ra commands1là zero. Nếu mã thoát là khác không, thì elsemệnh đề được thực thi. commands1có thể đơn giản hoặc phức tạp. Nó có thể, ví dụ, là một chuỗi của một hoặc nhiều đường ống ngăn cách bởi một trong những nhà khai thác ;, &, &&, hoặc ||. Các ifđiều kiện hiển thị dưới đây chỉ là trường hợp đặc biệt của commands1:

  1. if [ condition ]

    Đây là testlệnh shell truyền thống . Nó có sẵn trên tất cả các vỏ POSIX. Lệnh kiểm tra đặt mã thoát và ifcâu lệnh hoạt động tương ứng. Các xét nghiệm điển hình là liệu một tệp tồn tại hay một số bằng với một số khác.

  2. if [[ condition ]]

    Đây là một biến thể nâng cấp mới testtừ kshbashzsh cũng hỗ trợ. testLệnh này cũng đặt mã thoát và ifcâu lệnh hoạt động tương ứng. Trong số các tính năng mở rộng của nó, nó có thể kiểm tra xem một chuỗi có khớp với biểu thức chính quy hay không.

  3. if ((condition))

    Một phần mở rộng ksh khác mà bashzsh cũng hỗ trợ. Điều này thực hiện số học. Theo kết quả của số học, một mã thoát được thiết lập và ifcâu lệnh hoạt động tương ứng. Nó trả về mã thoát bằng 0 (true) nếu kết quả tính toán số học là khác không. Giống như [[...]], hình thức này không phải là POSIX và do đó không thể mang theo được.

  4. if (command)

    Điều này chạy lệnh trong một subshell. Khi lệnh hoàn thành, nó đặt mã thoát và ifcâu lệnh hoạt động tương ứng.

    Một lý do điển hình cho việc sử dụng một subshell như thế này là để hạn chế tác dụng phụ của commandnếu commandcần các bài tập biến hoặc thay đổi khác với môi trường của vỏ. Những thay đổi như vậy không còn lại sau khi lớp con hoàn thành.

  5. if command

    lệnh được thực thi và ifcâu lệnh hoạt động theo mã thoát của nó.


24
Cảm ơn bao gồm tùy chọn thứ 5. Đó là chìa khóa để hiểu cách thức này thực sự hoạt động và được sử dụng một cách đáng ngạc nhiên.
gà con

4
Lưu ý rằng đó [thực sự là một nhị phân, không phải là một lệnh hoặc biểu tượng nội bộ. Nói chung là sống trong /bin.
Julien R.

8
@JulienR. thực sự [là một xây dựng trong, như là test. Có phiên bản nhị phân có sẵn cho lý do tương thích. Kiểm tra help [help test.
OldTimer

4
Đáng lưu ý rằng trong khi ((không phải là POSIX, $((tức là mở rộng số học và rất dễ nhầm lẫn với chúng. Thông thường, một cách giải quyết là sử dụng một cái gì đó như [ $((2+2)) -eq 4 ]sử dụng số học trong các báo cáo chính thức
Sergiy Kolodyazhnyy

1
Tôi ước tôi có thể bình chọn câu trả lời này nhiều lần. Giải thích hoàn hảo.
Anthony Gatlin

77
  • (…)dấu ngoặc chỉ ra một nhánh con . Những gì bên trong chúng không phải là một biểu thức như trong nhiều ngôn ngữ khác. Đây là danh sách các lệnh (giống như dấu ngoặc đơn bên ngoài). Các lệnh này được thực thi trong một quy trình con riêng biệt, do đó, bất kỳ chuyển hướng, gán, v.v. được thực hiện bên trong dấu ngoặc đơn đều không có tác dụng bên ngoài dấu ngoặc đơn.
    • Với ký hiệu đô la hàng đầu, $(…)là sự thay thế lệnh : có một lệnh bên trong dấu ngoặc đơn và đầu ra từ lệnh được sử dụng như một phần của dòng lệnh (sau khi mở rộng thêm trừ khi thay thế nằm giữa dấu ngoặc kép, nhưng đó là một câu chuyện khác ) .
  • { … }dấu ngoặc nhọn giống như dấu ngoặc đơn ở chỗ chúng nhóm các lệnh, nhưng chúng chỉ ảnh hưởng đến phân tích cú pháp, không nhóm. Chương trình x=2; { x=4; }; echo $xin 4, trong khi x=2; (x=4); echo $xin 2. (Ngoài ra, dấu ngoặc phải là các từ khóa cần được phân tách và tìm thấy ở vị trí lệnh (do đó, khoảng trắng sau {;trước }) trong khi dấu ngoặc đơn thì không. Đó chỉ là một cú pháp.
    • Với ký hiệu đô la hàng đầu, ${VAR}là một mở rộng tham số , mở rộng đến giá trị của một biến, với các biến đổi bổ sung có thể. Các ksh93vỏ cũng hỗ trợ ${ cmd;}như hình thức thay thế lệnh mà không đẻ trứng một subshell.
  • ((…))dấu ngoặc kép bao quanh một lệnh số học , nghĩa là tính toán trên các số nguyên, với một cú pháp giống với các ngôn ngữ lập trình khác. Cú pháp này chủ yếu được sử dụng cho các bài tập và trong các điều kiện. Điều này chỉ tồn tại trong ksh / bash / zsh, không tồn tại trong sh.
    • Cú pháp tương tự được sử dụng trong các biểu thức số học $((…)), mở rộng thành giá trị nguyên của biểu thức.
  • [ … ]dấu ngoặc đơn bao quanh biểu thức điều kiện . Các biểu thức điều kiện hầu hết được xây dựng trên các toán tử như -n "$variable"để kiểm tra xem một biến có trống không và -e "$file"để kiểm tra nếu một tệp tồn tại. Lưu ý rằng bạn cần một khoảng trắng xung quanh mỗi toán tử (ví dụ: [ "$x" = "$y" ]không [ "$x"="$y" ]) và khoảng trắng hoặc ký tự giống như ;cả bên trong và bên ngoài dấu ngoặc (ví dụ: [ -n "$foo" ]không [-n "$foo"]).
  • [[ … ]]dấu ngoặc kép là một dạng thay thế của các biểu thức điều kiện trong ksh / bash / zsh với một vài tính năng bổ sung, ví dụ bạn có thể viết [[ -L $file && -f $file ]]để kiểm tra xem một tệp có phải là một liên kết tượng trưng đến một tệp thông thường trong khi các dấu ngoặc đơn yêu cầu [ -L "$file" ] && [ -f "$file" ]. Xem Tại sao mở rộng tham số với khoảng trắng không có dấu ngoặc kép hoạt động trong dấu ngoặc kép [[nhưng không phải dấu ngoặc đơn [? để biết thêm về chủ đề này.

Trong shell, mỗi lệnh là một lệnh có điều kiện: mọi lệnh đều có trạng thái trả về là 0 biểu thị thành công hoặc số nguyên nằm trong khoảng từ 1 đến 255 (và có khả năng nhiều hơn trong một số shell) cho thấy lỗi. Các [ … ]lệnh (hoặc [[ … ]]hình thức cú pháp) là một lệnh đặc biệt mà cũng có thể được đánh vần test …và thành công khi một tập tin tồn tại, hoặc khi một chuỗi không bị để trống, hoặc khi một số nhỏ hơn khác, vv ((…))hình thức cú pháp thành công khi một số là khác không. Dưới đây là một vài ví dụ về các điều kiện trong tập lệnh shell:

  • Kiểm tra nếu myfilechứa chuỗi hello:

    if grep -q hello myfile; then 
  • Nếu mydirlà một thư mục, thay đổi nó và làm công cụ:

    if cd mydir; then
      echo "Creating mydir/myfile"
      echo 'some content' >myfile
    else
      echo >&2 "Fatal error. This script requires mydir to exist."
    fi
  • Kiểm tra nếu có một tệp được gọi myfiletrong thư mục hiện tại:

    if [ -e myfile ]; then 
  • Tương tự, nhưng cũng bao gồm các liên kết tượng trưng lơ lửng:

    if [ -e myfile ] || [ -L myfile ]; then 
  • Kiểm tra xem giá trị của x(được coi là số) ít nhất là 2, có thể:

    if [ "$x" -ge 2 ]; then 
  • Kiểm tra xem giá trị của x(được coi là số) ít nhất là 2, trong bash / ksh / zsh:

    if ((x >= 2)); then 

Lưu ý rằng dấu ngoặc đơn hỗ trợ -athay vì &&, vì vậy người ta có thể viết : [ -L $file -a -f $file ], cùng số lượng ký tự trong dấu ngoặc mà không cần thêm []...
Alexis Wilke

6
@AlexisWilke Các toán tử -a-ocó vấn đề vì chúng có thể dẫn đến các phân tích cú pháp không chính xác nếu một số toán hạng liên quan trông giống như các toán tử. Đó là lý do tại sao tôi không đề cập đến chúng: chúng không có lợi thế và không phải lúc nào cũng hoạt động. Và không bao giờ viết các mở rộng biến không được trích dẫn mà không có lý do chính đáng: [[ -L $file -a -f $file ]]vẫn ổn nhưng với dấu ngoặc đơn bạn cần [ -L "$file" -a -f "$file" ](điều này ổn, ví dụ như nếu $fileluôn luôn bắt đầu bằng /hoặc ./).
Gilles

Lưu ý rằng đó là [[ -L $file && -f $file ]](không -a[[...]]biến thể).
Stéphane Chazelas

18

Từ tài liệu bash :

(list)danh sách được thực thi trong môi trường mạng con (xem MÔI TRƯỜNG THỰC HIỆN QUY TẮC bên dưới). Các phép gán biến và các lệnh dựng sẵn ảnh hưởng đến môi trường của shell không còn hiệu lực sau khi lệnh hoàn thành. Trạng thái trả về là trạng thái thoát của danh sách.

Nói cách khác, bạn đảm bảo rằng bất cứ điều gì xảy ra trong 'danh sách' (như a cd) đều không có tác dụng ngoài (). Điều duy nhất mà sẽ bị rò rỉ là mã lối ra của lệnh cuối cùng hoặc với set -elệnh đầu tiên mà tạo ra một lỗi (khác hơn là một vài ví dụ như if, while, vv)

((expression))Biểu thức được đánh giá theo các quy tắc được mô tả dưới đây trong ĐÁNH GIÁ ARITHMETIC. Nếu giá trị của biểu thức là khác không, trạng thái trả về là 0; mặt khác, trạng thái trả về là 1. Điều này hoàn toàn tương đương với "biểu thức".

Đây là một phần mở rộng bash cho phép bạn làm toán. Điều này hơi giống với việc sử dụng exprmà không có tất cả các giới hạn của expr(chẳng hạn như có không gian ở mọi nơi, thoát *, v.v.)

[[ expression ]]Trả về trạng thái 0 hoặc 1 tùy theo đánh giá biểu thức biểu thức điều kiện. Biểu thức bao gồm các bầu cử sơ bộ được mô tả dưới đây trong GIẢI THÍCH ĐIỀU KIỆN. Chia tách từ và mở rộng tên đường dẫn không được thực hiện trên các từ giữa [[và]]; mở rộng dấu ngã, mở rộng tham số và biến, mở rộng số học, thay thế lệnh, thay thế quá trình và loại bỏ trích dẫn được thực hiện. Các toán tử có điều kiện như -f phải không được trích dẫn để được công nhận là nguyên tắc.

Khi được sử dụng với [[, các toán tử <và> sắp xếp từ vựng theo ngôn ngữ hiện tại.

Điều này cung cấp một bài kiểm tra nâng cao để so sánh các chuỗi, số và tệp giống như testcác ưu đãi, nhưng mạnh mẽ hơn.

[ expr ]Trả về trạng thái 0 (đúng) hoặc 1 (sai) tùy theo đánh giá của biểu thức điều kiện expr. Mỗi toán tử và toán tử và phải là một đối số riêng biệt. Biểu thức bao gồm các nguyên tắc được mô tả ở trên trong GIẢI THÍCH ĐIỀU KIỆN. kiểm tra không chấp nhận bất kỳ tùy chọn nào, cũng không chấp nhận và bỏ qua một đối số của - như biểu thị sự kết thúc của các tùy chọn.

[...]

Cái này gọi test. Thật ra, ngày xưa, [là một liên kết tượng trưng cho test. Nó hoạt động theo cùng một cách và bạn có những hạn chế tương tự. Vì một nhị phân biết tên mà nó đã được bắt đầu, chương trình thử nghiệm có thể phân tích các tham số cho đến khi tìm thấy một tham số ]. Thủ thuật Unix vui nhộn.

Lưu ý rằng trong trường hợp bash, [testđược tích hợp theo chức năng (như đã đề cập trong một chú thích), nhưng khá nhiều những hạn chế tương tự áp dụng.


1
Mặc dù test[tất nhiên là các lệnh dựng sẵn trong Bash, nhưng có khả năng là một nhị phân bên ngoài cũng tồn tại.
ilkkachu

1
Nhị phân bên ngoài [không phải là một liên kết tượng trưng cho testhầu hết các hệ thống hiện đại.
Random832

1
Bằng cách nào đó, tôi thấy thật buồn cười khi họ bận tâm tạo ra hai nhị phân riêng biệt, cả hai đều có chính xác những gì họ cần, thay vì chỉ kết hợp chúng và thêm một vài điều kiện. Mặc dù thực tế strings /usr/bin/testcho thấy nó cũng có văn bản trợ giúp, vì vậy tôi không biết phải nói gì.
ilkkachu

2
@ Random832 Tôi có quan điểm của bạn về lý do GNU để tránh hành vi arg0 bất ngờ nhưng về các yêu cầu POSIX, tôi sẽ không khẳng định như vậy. Mặc dù testlệnh rõ ràng là cần thiết để tồn tại dưới dạng một lệnh dựa trên tệp độc lập, nhưng không có gì trong đó nói rằng [biến thể của nó cũng cần phải được thực hiện theo cách đó. Ví dụ, Solaris 11 không cung cấp bất kỳ [thực thi nào nhưng vẫn hoàn toàn tuân thủ các tiêu chuẩn POSIX
jlliagre

2
(lối ra 1) có hiệu ứng bên ngoài dấu ngoặc đơn.
Alexander

14

[ đấu với [[

Câu trả lời này sẽ bao gồm các [vs [[tập hợp các câu hỏi.

Một số khác biệt về Bash 4.3.11:

  • Gia hạn POSIX vs Bash:

  • lệnh thường xuyên vs ma thuật

    • [ chỉ là một lệnh thông thường với một cái tên kỳ lạ.

      ]chỉ là một đối số [ngăn chặn các đối số tiếp theo được sử dụng.

      Ubuntu 16.04 thực sự có một tệp thực thi cho nó /usr/bin/[được cung cấp bởi coreutils, nhưng phiên bản tích hợp bash được ưu tiên.

      Không có gì được thay đổi theo cách Bash phân tích lệnh.

      Cụ thể, <là chuyển hướng &&||nối nhiều lệnh, ( )tạo các lớp con trừ khi thoát ra \và mở rộng từ diễn ra như bình thường.

    • [[ X ]]là một cấu trúc duy nhất Xđược phân tích cú pháp một cách kỳ diệu. <, &&, ||()được đối xử đặc biệt, và các quy tắc từ tách khác nhau.

      Ngoài ra còn có sự khác biệt như ==~.

      Trong Bashese: [là một lệnh tích hợp và [[là một từ khóa: https://askubfox.com/questions/445749/whats-the-difference-b between-shell-buildin-and-shell - keyword

  • <

  • &&||

    • [[ a = a && b = b ]]: đúng, hợp lý
    • [ a = a && b = b ]: lỗi cú pháp, được &&phân tích cú pháp như một dấu tách lệnh ANDcmd1 && cmd2
    • [ a = a -a b = b ]: tương đương, nhưng không được chấp nhận bởi POSIX³
    • [ a = a ] && [ b = b ]: POSIX và tương đương đáng tin cậy
  • (

    • [[ (a = a || a = b) && a = b ]]: sai trái
    • [ ( a = a ) ]: lỗi cú pháp, ()được hiểu là một nhánh con
    • [ \( a = a -o a = b \) -a a = b ]: tương đương, nhưng ()không được chấp nhận bởi POSIX
    • { [ a = a ] || [ a = b ]; } && [ a = b ]POSIX tương đương 5
  • tách từ và tạo tên tệp khi mở rộng (split + global)

    • x='a b'; [[ $x = 'a b' ]]: đúng, trích dẫn không cần thiết
    • x='a b'; [ $x = 'a b' ]: lỗi cú pháp, mở rộng thành [ a b = 'a b' ]
    • x='*'; [ $x = 'a b' ]: lỗi cú pháp nếu có nhiều hơn một tệp trong thư mục hiện tại.
    • x='a b'; [ "$x" = 'a b' ]: Tương đương POSIX
  • =

    • [[ ab = a? ]]: đúng, bởi vì nó không khớp mẫu ( * ? [là ma thuật). Không toàn cầu mở rộng để tập tin trong thư mục hiện tại.
    • [ ab = a? ]: toàn a?cầu mở rộng. Vì vậy, có thể đúng hoặc sai tùy thuộc vào các tệp trong thư mục hiện tại.
    • [ ab = a\? ]: sai, không mở rộng toàn cầu
    • ===giống nhau ở cả hai [[[, nhưng ==là một phần mở rộng Bash.
    • case ab in (a?) echo match; esac: Tương đương POSIX
    • [[ ab =~ 'ab?' ]]: sai 4 , mất phép thuật với''
    • [[ ab? =~ 'ab?' ]]: thật
  • =~

    • [[ ab =~ ab? ]]: đúng, POSIX kết hợp biểu thức chính quy mở rộng , ?không mở rộng toàn cầu
    • [ a =~ a ]: lỗi cú pháp. Không có bash tương đương.
    • printf 'ab\n' | grep -Eq 'ab?': Tương đương POSIX (chỉ dữ liệu một dòng)
    • awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?': Tương đương POSIX.

Khuyến cáo : luôn luôn sử dụng [].

Có tương đương POSIX cho mọi [[ ]]cấu trúc tôi đã thấy.

Nếu bạn sử dụng [[ ]]bạn:

  • mất tính di động
  • buộc người đọc phải tìm hiểu sự phức tạp của một phần mở rộng bash khác. [chỉ là một lệnh thông thường với một cái tên kỳ lạ, không có ngữ nghĩa đặc biệt nào được tham gia.

Lấy cảm hứng từ [[...]]cấu trúc tương đương trong vỏ Korn

² nhưng không thành công đối với một số giá trị ahoặc b(như +hoặc index) và không so sánh số nếu abtrông giống như số nguyên thập phân. expr "x$a" '<' "x$b"làm việc xung quanh cả hai.

Và cũng thất bại đối với một số giá trị ahoặc bthích !hoặc (.

4 trong bash 3.2 trở lên và cung cấp khả năng tương thích với bash 3.1 không được bật (như với BASH_COMPAT=3.1)

5 mặc dù việc nhóm (ở đây với {...;}nhóm lệnh thay vì (...)chạy một lớp con không cần thiết) là không cần thiết vì các toán tử shell ||&&(trái ngược với các toán tử ||&& [[...]]toán tử -o/ -a [toán tử) có quyền ưu tiên như nhau. Như vậy [ a = a ] || [ a = b ] && [ a = b ]sẽ tương đương.


@ StéphaneChazelas cảm ơn vì thông tin! Tôi đã thêm vào exprcâu trả lời. Thuật ngữ "Phần mở rộng Bash" không có nghĩa là Bash là vỏ đầu tiên để thêm một số cú pháp, học POSIX sh vs Bash đã đủ khiến tôi phát điên.
Ciro Santilli 心 心

Xem man testnếu bạn đã cố gắng man [và bị lạc. Điều đó sẽ giải thích biến thể POSIX.
Jonathan Komar

13

Vài ví dụ:

Kiểm tra truyền thống:

foo="some thing"
# check if value of foo is not empty
if [ -n "$foo" ] ; then... 
if test -n "$foo" ; then... 

test[là các lệnh như bất kỳ lệnh nào khác, vì vậy biến được chia thành các từ trừ khi nó nằm trong dấu ngoặc kép.

Thử nghiệm kiểu mới

[[ ... ]] là một cấu trúc shell đặc biệt (mới hơn), hoạt động hơi khác một chút, điều rõ ràng nhất là nó không có các biến phân tách từ:

if [[ -n $foo ]] ; then... 

Một số tài liệu trên [[[ở đây .

Kiểm tra số học:

foo=12 bar=3
if (( $foo + $bar == 15 )) ; then ...  

Các lệnh "Bình thường":

Tất cả các hành động trên giống như các lệnh thông thường và ifcó thể nhận bất kỳ lệnh nào:

# grep returns true if it finds something
if grep pattern file ; then ...

Nhiều lệnh:

Hoặc chúng ta có thể sử dụng nhiều lệnh. Gói một tập hợp các lệnh để ( ... )chạy chúng trong lớp con, tạo một bản sao tạm thời trạng thái của shell (thư mục làm việc, các biến). Nếu chúng ta cần chạy một số chương trình tạm thời trong một thư mục khác:

# this will move to $somedir only for the duration of the subshell 
if ( cd $somedir ; some_test ) ; then ...

# while here, the rest of the script will see the new working
# directory, even after the test
if cd $somedir ; some_test ; then ...

1

Các lệnh nhóm

Bash cung cấp hai cách để nhóm một danh sách các lệnh sẽ được thực thi như một đơn vị.

( list )Việc đặt một danh sách các lệnh giữa các dấu ngoặc đơn sẽ tạo ra một môi trường lớp con và mỗi lệnh trong danh sách sẽ được thực thi trong lớp con đó. Vì danh sách được thực thi trong một lớp con, các phép gán biến không còn hiệu lực sau khi lớp con hoàn thành.

$ a=1; (a=2; echo "inside: a=$a"); echo "outside: a=$a"
inside: a=2
outside: a=1

{ list; }Đặt một danh sách các lệnh giữa các dấu ngoặc nhọn làm cho danh sách được thực thi trong bối cảnh shell hiện tại . Không có subshell được tạo ra. Danh sách dấu chấm phẩy (hoặc dòng mới) là bắt buộc. Nguồn

${} Parameter expansion Ex:  ANIMAL=duck; echo One $ANIMAL, two ${ANIMAL}s
$() Command substitution Ex: result=$(COMMAND) 
$(()) Arithmetic expansion Ex: var=$(( 20 + 5 )) 

Xây dựng có điều kiện

Giá đỡ đơn tức là []
Để so sánh ==, !=, <,>nên được sử dụng và để so sánh số eq, ne,ltgtnên được sử dụng.

Chân đế nâng cao tức là[[]]

Trong tất cả các ví dụ trên, chúng tôi chỉ sử dụng dấu ngoặc đơn để đặt biểu thức điều kiện, nhưng bash cho phép dấu ngoặc kép đóng vai trò là phiên bản nâng cao của cú pháp dấu ngoặc đơn.

Để so sánh ==, !=, <,>có thể sử dụng theo nghĩa đen.

  • [là một từ đồng nghĩa với lệnh kiểm tra. Ngay cả khi nó được tích hợp vào vỏ, nó sẽ tạo ra một quy trình mới.
  • [[ là một phiên bản cải tiến mới của nó, là một từ khóa, không phải là một chương trình.
  • [[được hiểu bởi KornBash.

Nguồ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.