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 bên trong dấu ngoặc kép [[nhưng không nằm trong dấu ngoặc đơn [[]?


85

Tôi bối rối với việc sử dụng dấu ngoặc đơn hoặc đôi. Nhìn vào mã này:

dir="/home/mazimi/VirtualBox VMs"

if [[ -d ${dir} ]]; then
    echo "yep"
fi

Nó hoạt động hoàn hảo mặc dù chuỗi chứa một khoảng trắng. Nhưng khi tôi thay đổi nó thành một dấu ngoặc đơn:

dir="/home/mazimi/VirtualBox VMs"

if [ -d ${dir} ]; then
    echo "yep"
fi

Nó nói rằng:

./script.sh: line 5: [: /home/mazimi/VirtualBox: binary operator expected

Khi tôi thay đổi nó thành:

dir="/home/mazimi/VirtualBox VMs"

if [ -d "${dir}" ]; then
    echo "yep"
fi

Nó hoạt động tốt. Ai đó có thể giải thích những gì đang xảy ra? Khi nào tôi nên gán dấu ngoặc kép xung quanh các biến như "${var}"để ngăn sự cố do khoảng trắng gây ra?



Một câu hỏi chung chung hơn: unix.stackexchange.com/questions/306111/iêng
Ciro Santilli

Câu trả lời:


83

Dấu ngoặc đơn [thực sự là một bí danh cho testlệnh, nó không phải là cú pháp.

Một trong những nhược điểm (của nhiều) của dấu ngoặc đơn là nếu một hoặc nhiều toán hạng mà nó đang cố gắng đánh giá trả về một chuỗi rỗng, nó sẽ phàn nàn rằng nó đang mong đợi hai toán hạng (nhị phân). Đây là lý do tại sao bạn thấy mọi người làm [ x$foo = x$blah ], xđảm bảo rằng toán hạng sẽ không bao giờ đánh giá thành một chuỗi trống.

Mặt khác [[ ]], khung đôi là cú pháp và có khả năng hơn nhiều [ ]. Như bạn đã tìm ra, nó không có vấn đề toán hạng đơn và nó cũng cho phép cú pháp giống C hơn với các >, <, >=, <=, !=, ==, &&, ||toán tử.

Đề nghị của tôi là như sau: Nếu thông dịch viên của bạn là #!/bin/bash, thì hãy luôn sử dụng[[ ]]

Điều quan trọng cần lưu ý [[ ]]là không được hỗ trợ bởi tất cả các trình bao POSIX, tuy nhiên nhiều trình bao hỗ trợ nó như zshkshngoàibash


16
Vấn đề chuỗi rỗng (và nhiều vấn đề khác) được giải quyết bằng cách sử dụng dấu ngoặc kép. "X" là để giải quyết một loại vấn đề khác: trong đó toán hạng có thể được coi là toán tử . Giống như khi $foo!hay (hay -n... Vấn đề đó không có nghĩa là trở thành một vấn đề với vỏ POSIX nơi số lượng đối số (bên cạnh []) không lớn hơn bốn.
Stéphane Chazelas

21
Để làm rõ, [ x$foo = x$blah ]chỉ đơn giản là sai như [ $foo = $bar ]. [ "$foo" = "$bar" ]là chính xác trong bất kỳ vỏ tuân thủ POSIX nào, [ "x$foo" = "x$bar" ]sẽ hoạt động trong mọi vỏ giống như Bourne, nhưng xkhông phải cho các trường hợp bạn có chuỗi rỗng mà $foocó thể !hoặc -n...
Stéphane Chazelas

1
Ngữ nghĩa thú vị trong câu trả lời này. Tôi không chắc ý của bạn là gì bởi cú pháp * không * . Tất nhiên, [không chính xác là một bí danh cho test. Nếu có, thì testsẽ chấp nhận một khung vuông đóng. test -n foo ]. Nhưng testkhông, và [đòi hỏi một. [giống hệt như testtrong tất cả các khía cạnh khác, nhưng đó không phải là cách aliashoạt động. Bash mô tả [testnhư các nội dung shell , nhưng [[là một từ khóa shell .
kojiro

1
Chà, trong bash [là shell dựng sẵn, nhưng / usr / bin / [cũng là một tệp thực thi. Theo truyền thống, nó là một liên kết đến / usr / bin / test, nhưng trong lõi gnu hiện đại là một nhị phân riêng biệt. Phiên bản thử nghiệm truyền thống kiểm tra argv [0] để xem liệu nó có được gọi là [và sau đó tìm kiếm sự phù hợp].
Evan

Bash của tôi không hoạt động với >=<=
Sinh viên

51

Các [lệnh là một lệnh thông thường. Mặc dù hầu hết các shell cung cấp cho nó như là một tích hợp cho hiệu quả, nó tuân theo các quy tắc cú pháp thông thường của shell. [là chính xác tương đương test, ngoại trừ [yêu cầu ]như là đối số cuối cùng của nó và testkhông.

Dấu ngoặc kép [[ … ]]là cú pháp đặc biệt. Chúng được giới thiệu trong ksh (vài năm sau [) vì [có thể gây rắc rối khi sử dụng chính xác và [[cho phép một số bổ sung mới tốt đẹp sử dụng các ký tự đặc biệt shell. Ví dụ, bạn có thể viết

[[ $x = foo && $y = bar ]]

bởi vì toàn bộ biểu thức điều kiện được phân tách bằng shell, trong khi [ $x = foo && $y = bar ]trước tiên sẽ được tách thành hai lệnh [ $x = foovà được $y = bar ]phân tách bởi &&toán tử. Tương tự dấu ngoặc kép cho phép những thứ như cú pháp khớp mẫu, ví dụ [[ $x == a* ]]để kiểm tra xem giá trị xbắt đầu bằng a; trong dấu ngoặc đơn, điều này sẽ mở rộng a*sang danh sách các tệp có tên bắt đầu atrong thư mục hiện tại. Dấu ngoặc kép được giới thiệu lần đầu tiên trong ksh và chỉ có sẵn trong ksh, bash và zsh.

Trong dấu ngoặc đơn, bạn cần sử dụng dấu ngoặc kép xung quanh các thay thế khác nhau, như ở hầu hết các vị trí khác, vì chúng chỉ là đối số của một lệnh (điều này xảy ra là [lệnh). Trong dấu ngoặc kép, bạn không cần dấu ngoặc kép, vì shell không thực hiện phân tách từ hoặc tạo khối: nó phân tích một biểu thức điều kiện, không phải là lệnh.

Mặc dù vậy, một ngoại lệ là [[ $var1 = "$var2" ]]nơi bạn cần dấu ngoặc kép nếu bạn muốn thực hiện so sánh chuỗi byte-byte, nếu không, $var2sẽ là một mẫu để khớp $var1với.

Một điều bạn không thể làm [[ … ]]là sử dụng một biến như một toán tử. Ví dụ: điều này là hoàn toàn hợp pháp (nhưng hiếm khi hữu ích):

if [ -n "$reverse_sort" ]; then op=-gt; else op=-lt; fi

if [ "$x" "$op" "$y" ]; then 

Trong ví dụ của bạn

dir="/home/mazimi/VirtualBox VMs"
if [ -d ${dir} ]; then 

lệnh bên trong if[với 4 đối số -d, /home/mazimi/VirtualBox, VMs]. Shell phân tích cú pháp -d /home/mazimi/VirtualBoxvà sau đó không biết phải làm gì với VMs. Bạn sẽ cần phải ngăn chặn việc chia tách từ ${dir}để có được một lệnh được hình thành tốt.

Nói chung, luôn luôn sử dụng dấu ngoặc kép xung quanh các thay thế biến và lệnh trừ khi bạn biết bạn muốn thực hiện chia tách từ và kết quả trên kết quả. Những nơi chính mà an toàn không sử dụng dấu ngoặc kép là:

  • trong một bài tập: foo=$bar(nhưng lưu ý rằng bạn cần dấu ngoặc kép trong export "foo=$bar"hoặc trong các bài tập mảng như array=("$a" "$b"));
  • trong một casetuyên bố : case $foo in …;
  • bên trong dấu ngoặc kép ngoại trừ ở phía bên phải của toán tử =hoặc ==toán tử (trừ khi bạn muốn khớp mẫu) : [[ $x = "$y" ]].

Trong tất cả những điều này, việc sử dụng dấu ngoặc kép là chính xác, vì vậy bạn cũng có thể bỏ qua các quy tắc nâng cao và sử dụng dấu ngoặc kép mọi lúc.


1
@StephaneChazelas Một sai lầm của tôi. Hóa ra đó [thực sự là từ Hệ thống III năm 1981 , tôi nghĩ nó đã cũ hơn.
Gilles

8

Khi nào tôi nên gán dấu ngoặc kép xung quanh các biến như "${var}"để ngăn sự cố do khoảng trắng gây ra?

Ẩn ý trong câu hỏi này là

Tại sao không đủ tốt?${variable_name}

${variable_name} không có nghĩa là những gì bạn nghĩ nó làm

Nếu bạn nghĩ rằng nó có bất cứ điều gì liên quan đến các vấn đề gây ra bởi khoảng trắng (trong các giá trị biến). là tốt cho việc này:${variable_name}

$ bar=foo
$ bard=Shakespeare
$ echo $bard
Shakespeare
$ echo ${bar}d
food

và không có gì khác! 1  không làm bất kỳ tốt trừ khi bạn đang ngay sau đó với một nhân vật có thể là một phần của một tên biến: một lá thư (-hoặc-), một dấu gạch dưới (), hoặc một chữ số (-). Và thậm chí sau đó, bạn có thể làm việc xung quanh nó:${variable_name}AZaz_09

$ echo "$bar"d
food

Tôi không cố gắng ngăn cản việc sử dụng nó - echo "${bar}d"có lẽ là giải pháp tốt nhất ở đây - nhưng không khuyến khích mọi người dựa vào niềng răng thay vì dấu ngoặc kép, hoặc áp dụng niềng răng theo bản năng và sau đó hỏi, ngay bây giờ, tôi có cần báo giá không? ”  Bạn luôn nên sử dụng dấu ngoặc kép trừ khi bạn có lý do chính đáng không, và bạn chắc chắn bạn biết những gì bạn đang làm.
_________________
1    Trừ, tất nhiên, một thực tế là các hình thức fancier của việc mở rộng tham số , ví dụ, và , xây dựng trên cú pháp. Ngoài ra, bạn cần sử dụng , v.v., để tham chiếu ngày 10, 11, v.v., các tham số vị trí - trích dẫn sẽ không giúp bạn điều đó.${parameter:-[word]}${parameter%[word]}${parameter}${10}${11}


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.