Là dấu ngoặc vuông đôi [[]] thích hợp hơn dấu ngoặc đơn [] trong Bash?


574

Một đồng nghiệp đã tuyên bố gần đây trong một đánh giá mã rằng [[ ]]cấu trúc sẽ được ưu tiên hơn[ ] trong các cấu trúc như

if [ "`id -nu`" = "$someuser" ] ; then 
     echo "I love you madly, $someuser"
fi

Anh ta không thể cung cấp một lý do. Có một cái không?


19
Hãy linh hoạt, đôi khi cho phép bản thân nghe một lời khuyên mà không yêu cầu lời giải thích sâu sắc :) Đối [[với mã đó là tốt và rõ ràng, nhưng hãy nhớ ngày hôm đó khi bạn chuyển tập lệnh của bạn trên hệ thống với vỏ mặc định không bashhoặc ksh, vv [là xấu hơn, cồng kềnh, nhưng hoạt động như AK-47trong mọi tình huống.
rook

178
@rook Bạn có thể nghe một lời khuyên mà không cần một lời giải thích sâu sắc, chắc chắn. Nhưng khi bạn yêu cầu một lời giải thích và không nhận được nó, đó thường là một lá cờ đỏ. "Tin tưởng, nhưng xác minh" và tất cả điều đó.
Josip Rodin


1
@rook nói cách khác "hãy làm những gì bạn đã nói và đừng đặt câu hỏi"
glyph

@rook Điều đó thật vô nghĩa.
Rakib

Câu trả lời:


607

[[có ít bất ngờ hơn và thường an toàn hơn khi sử dụng. Nhưng nó không phải là di động - POSIX không chỉ định những gì nó làm và chỉ một số shell hỗ trợ nó (bên cạnh bash, tôi nghe nói ksh cũng hỗ trợ nó). Ví dụ, bạn có thể làm

[[ -e $b ]]

để kiểm tra xem một tập tin tồn tại. Nhưng với [, bạn phải trích dẫn $b, bởi vì nó phân tách đối số và mở rộng những thứ như "a*"(nơi [[lấy nó theo nghĩa đen). Điều đó cũng liên quan đến việc làm thế nào [có thể là một chương trình bên ngoài và nhận được đối số của nó giống như mọi chương trình khác (mặc dù nó cũng có thể là một phần mềm dựng sẵn, nhưng sau đó nó vẫn không xử lý đặc biệt này).

[[cũng có một số tính năng hay khác, như kết hợp biểu thức chính quy =~cùng với các toán tử như chúng được biết đến bằng các ngôn ngữ giống như C. Đây là một trang tốt về nó: sự khác biệt giữa thử nghiệm [và là [[gì? Bash Test


21
Xem xét rằng bash có ở khắp mọi nơi trong những ngày này, tôi có xu hướng nghĩ rằng nó khá di động. Ngoại lệ phổ biến duy nhất đối với tôi là trên các nền tảng busybox - nhưng có lẽ bạn muốn thực hiện một nỗ lực đặc biệt cho dù sao đi nữa, với phần cứng mà nó chạy.
súng

14
@ súng: Thật vậy. Tôi đề nghị rằng câu thứ hai của bạn từ chối câu đầu tiên của bạn; nếu bạn xem xét việc phát triển phần mềm nói chung, "bash có ở mọi nơi" và "ngoại lệ là nền tảng busybox" hoàn toàn không tương thích. busybox là phổ biến cho phát triển nhúng.
Các cuộc đua Lightness trong quỹ đạo

11
@guns: Ngay cả khi bash được cài đặt trên hộp của tôi, nó có thể không thực thi tập lệnh (tôi có thể có / bin / sh được liên kết với một số biến thể dấu gạch ngang, như là tiêu chuẩn trên FreeBSD và Ubuntu). Ngoài ra còn có sự kỳ lạ với Busybox: với các tùy chọn biên dịch tiêu chuẩn hiện nay, nó phân tích cú pháp [[ ]]nhưng diễn giải nó có nghĩa giống như [ ].
dubiousjim

59
@dubiousjim: Nếu bạn sử dụng các cấu trúc chỉ bash (như cái này), bạn nên có #! / Bin / bash, không phải #! / bin / sh.
Nick Matteo

16
Đó là lý do tại sao tôi làm cho các tập lệnh của mình sử dụng #!/bin/shnhưng sau đó chuyển chúng sang sử dụng #!/bin/bashngay khi tôi dựa vào một số tính năng cụ thể của BASH, để biểu thị nó không còn là vỏ Bourne di động.
anthony

151

Khác biệt về hành vi

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 &&||ghép 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 keyword

  • <

  • &&||

    • [[ a = a && b = b ]]: đúng, hợp lý
    • [ a = a && b = b ]: lỗi cú pháp, && 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
    • [ ( 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 ] Tương đương POSIX
  • 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, vì nó 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? ]: 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?' ]]: false⁴, 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 (.

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)

⁵ 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 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.


4
Giải thích tốt. Tôi sử dụng [cho cùng một lý do.
Gordon

2
Trong tiếng Bashese :) ha. Làm rõ tốt đẹp về sự khác biệt và lý do để gắn bó với POSIX.
Jonathan Komar

56

[[ ]]có nhiều tính năng hơn - Tôi khuyên bạn nên xem Hướng dẫn về Script Bash nâng cao để biết thêm thông tin, cụ thể là phần lệnh thử nghiệm mở rộng trong Chương 7. Các thử nghiệm .

Ngẫu nhiên, như các ghi chú hướng dẫn, [[ ]]đã được giới thiệu trong ksh88 (phiên bản năm 1988 của vỏ Korn).


5
Điều này khác xa với một bashism, nó lần đầu tiên được giới thiệu trong vỏ Korn.
Henk Langeveld

6
@Thomas, ABS thực sự được coi là một tài liệu tham khảo rất kém trong nhiều vòng tròn; trong khi nó có rất nhiều thông tin chính xác, nó có xu hướng rất ít quan tâm để tránh thể hiện các thực hành xấu trong các ví dụ của nó, và đã dành phần lớn cuộc đời của nó không bị làm mờ.
Charles Duffy

@CharlesDuffy cảm ơn bình luận của bạn, bạn có thể đặt tên cho một sự thay thế tốt. Tôi không phải là một chuyên gia về kịch bản shell, tôi đang tìm một hướng dẫn mà tôi có thể tham khảo để viết một kịch bản khoảng nửa năm một lần.
Thomas

9
@Thomas, Wooledge BashFAQ và wiki liên quan là những gì tôi sử dụng; chúng được duy trì tích cực bởi những người từ chối kênh Freenode #bash (đôi khi, đôi khi gai góc, có xu hướng quan tâm sâu sắc đến tính đúng đắn). BashFAQ # 31, mywiki.wooledge.org/BashFAQ/031 , là trang liên quan trực tiếp đến câu hỏi này.
Charles Duffy

13

Từ so sánh, kiểm tra, khung, hoặc khung đôi, là nhanh nhất? ( http://bashcurescancer.com )

Dấu ngoặc kép là một lệnh ghép phức hợp, trong đó, khi kiểm tra và dấu ngoặc đơn là các hàm dựng sẵn (và trong thực tế là cùng một lệnh). Do đó, dấu ngoặc đơn và dấu ngoặc kép thực thi mã khác nhau.

Các thử nghiệm và khung đơn là dễ di chuyển nhất vì chúng tồn tại dưới dạng các lệnh riêng biệt và bên ngoài. Tuy nhiên, nếu bạn sử dụng bất kỳ phiên bản BASH hiện đại từ xa nào, khung đôi sẽ được hỗ trợ.


7
Điều gì với nỗi ám ảnh nhanh nhất trong các kịch bản shell? Tôi muốn nó di động nhất và không quan tâm đến việc cải thiện [[có thể mang lại. Nhưng sau đó, tôi là một rắm già của trường học cũ :-)
Jens

1
Tôi nghĩ rằng nhiều shell chứng minh các phiên bản dựng sẵn [testmặc dù các phiên bản bên ngoài cũng tồn tại.
dubiousjim

4
@ Nói chung tôi đồng ý: toàn bộ mục đích của tập lệnh là tính di động (là?) (Nếu không, chúng tôi sẽ mã & biên dịch, không phải tập lệnh) ... hai trường hợp ngoại lệ tôi có thể nghĩ đến là: (1) hoàn thành tab (trong đó kịch bản hoàn thành có thể thực sự dài với rất nhiều logic có điều kiện); và (2) siêu nhắc ( PS1=...crazy stuff...và / hoặc $PROMPT_COMMAND); đối với những điều này, tôi không muốn bất kỳ sự chậm trễ có thể cảm nhận được trong quá trình thực thi tập lệnh.
michael

1
Một số nỗi ám ảnh với nhanh nhất chỉ đơn giản là phong cách. Tất cả những thứ khác đều bằng nhau, tại sao không kết hợp cấu trúc mã hiệu quả hơn vào kiểu mặc định của bạn, đặc biệt nếu cấu trúc đó cũng mang lại khả năng đọc dễ dàng hơn? Về tính di động, rất nhiều nhiệm vụ phù hợp với bash vốn không mang tính di động. Ví dụ, tôi cần chạy apt-get updatenếu đã hơn X giờ kể từ lần chạy cuối cùng. Thật là nhẹ nhõm khi người ta có thể bỏ tính di động khỏi danh sách các ràng buộc quá dài đối với mã.
Ron Burk

5

Nếu bạn đang theo hướng dẫn về phong cách của Google :

Kiểm tra, [[[

[[ ... ]]giảm lỗi vì không có sự mở rộng tên đường dẫn hoặc tách từ diễn ra giữa [[]], và [[ ... ]]cho phép khớp biểu thức chính quy ở nơi [ ... ]không.

# This ensures the string on the left is made up of characters in the
# alnum character class followed by the string name.
# Note that the RHS should not be quoted here.
# For the gory details, see
# E14 at https://tiswww.case.edu/php/chet/bash/FAQ
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
  echo "Match"
fi

# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
  echo "Match"
fi

# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
  echo "Match"
fi

1
Google đã viết " không có mở rộng tên đường dẫn ... diễn ra " nhưng [[ -d ~ ]]trả về đúng (ngụ ý ~đã được mở rộng thành /home/user). Tôi nghĩ rằng Google nên có chính xác hơn trong văn bản của nó.
JamesThomasMoon1979

2

Một tình huống điển hình mà bạn không thể sử dụng [[là trong tập lệnh configure.ac autotools, có dấu ngoặc có ý nghĩa đặc biệt và khác biệt, vì vậy bạn sẽ phải sử dụng testthay vì[ hoặc [[- Lưu ý rằng kiểm tra và [là cùng một chương trình.


2
Cho các autotool không phải là vỏ POSIX, tại sao bạn có thể mong đợi [được xác định là hàm shell POSIX?
Gordon

Bởi vì tập lệnh autoconf trông giống như tập lệnh shell và nó tạo ra tập lệnh shell và hầu hết các lệnh shell hoạt động bên trong nó.
vy32

-1

[[]] dấu ngoặc kép không được sắp xếp theo phiên bản nhất định của SunOS và hoàn toàn không bị thay đổi bên trong các khai báo hàm bởi: GNU bash, phiên bản 2.02.0 (1) -release (sparc-sun-solaris2.6)


1
rất đúng, và hoàn toàn không quan trọng. tính di động bash trên các phiên bản cũ hơn phải được xem xét. Mọi người nói "bash là phổ biến và di động, ngoại trừ có thể (chèn hệ điều hành bí truyền ở đây) " - nhưng theo kinh nghiệm của tôi, solaris là một trong những nền tảng mà phải chú ý đặc biệt đến tính di động: không chỉ xem xét các phiên bản bash cũ hơn trên HĐH mới hơn, các vấn đề / lỗi w / mảng, chức năng, v.v; nhưng ngay cả các tiện ích (được sử dụng trong các tập lệnh) như tr, sed, awk, tar có những điểm kỳ lạ & đặc thù trên solaris mà bạn phải làm việc xung quanh.
michael

2
bạn rất đúng ... rất nhiều tiện ích không phải là POSIX trên Solaris, chỉ cần nhìn vào đầu ra "df" và các đối số ... Xấu hổ trên Sun. Hy vọng rằng nó sẽ biến mất từng chút một (ngoại trừ ở Canada).
người nhặt rác

7
Solaris 2.6 nghiêm túc? Nó được phát hành vào năm 1997 và đã kết thúc hỗ trợ vào năm 2006. Tôi đoán nếu bạn vẫn đang sử dụng thì bạn có vấn đề khác!. Ngẫu nhiên, nó đã sử dụng Bash v2.02, đây là cái đã giới thiệu dấu ngoặc kép, do đó sẽ hoạt động ngay cả trên một cái gì đó cũ như vậy. Solaris 10 từ năm 2005 đã sử dụng Bash 3.2.51 và Solaris 11 từ năm 2011 sử dụng Bash 4.1.11.
peterh

1
Tính di động của Script. Trên các hệ thống Linux thường chỉ có phiên bản của mỗi công cụ và đó là phiên bản GNU. Trên Solaris, bạn thường có lựa chọn giữa phiên bản gốc Solaris hoặc phiên bản GNU (giả sử Solaris tar vs GNU tar). Nếu bạn phụ thuộc vào các phần mở rộng cụ thể của GNU thì bạn phải nói rằng trong tập lệnh của bạn để nó có thể mang theo được. Trên Solaris, bạn làm điều đó bằng cách thêm tiền tố vào "g", ví dụ `ggrep` nếu bạn muốn GNU grep.
peterh

-2

Tóm lại, [[tốt hơn vì nó không rẽ nhánh quá trình khác. Không có dấu ngoặc hoặc dấu ngoặc đơn nào chậm hơn dấu ngoặc kép vì nó tạo ra một quá trình khác.


8
Kiểm tra và [là tên cho cùng một lệnh dựng sẵn trong bash. Hãy thử sử dụng type [để thấy điều này.
AB

1
@alberge, điều đó đúng, nhưng [[, như khác biệt [, là cú pháp được diễn giải bởi trình thông dịch dòng lệnh bash. Trong bash, hãy thử gõ type [[. unix4linux là chính xác rằng mặc dù các [thử nghiệm shell Bourne cổ điển bỏ qua một quy trình mới để xác định giá trị thật, [[cú pháp (mượn từ ksh bởi bash, zsh, v.v.) thì không.
Tim Gilbert

2
@Tim, tôi không chắc bạn đang nói về vỏ Bourne nào, nhưng [được tích hợp sẵn cho Bash cũng như Dash ( /bin/shtrong tất cả các bản phân phối Linux có nguồn gốc từ Debian).
AB

1
Ồ, tôi hiểu ý của bạn, đó là sự thật. Tôi đã nghĩ về một cái gì đó như, nói, / bin / sh trên các hệ thống Solaris hoặc HP / UX cũ hơn, nhưng tất nhiên nếu bạn cần tương thích với những hệ thống bạn sẽ không sử dụng [[hoặc.
Tim Gilbert

1
@alberge Vỏ Bourne không phải là Bash (hay còn gọi là Bourne Again SHell ).
kiamlaluno
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.