Khi nào cần trích dẫn kép?


120

Lời khuyên cũ được sử dụng là trích dẫn kép bất kỳ biểu thức nào liên quan đến a $VARIABLE, ít nhất là nếu người ta muốn nó được giải thích bởi shell như một mục duy nhất, nếu không, bất kỳ khoảng trắng nào trong nội dung $VARIABLEsẽ bỏ vỏ.

Tuy nhiên, tôi hiểu rằng trong các phiên bản vỏ gần đây hơn, trích dẫn kép không còn luôn cần thiết (ít nhất là cho mục đích được mô tả ở trên). Ví dụ bash: trong :

% FOO='bar baz'
% [ $FOO = 'bar baz' ] && echo OK
bash: [: too many arguments
% [[ $FOO = 'bar baz' ]] && echo OK
OK
% touch 'bar baz'
% ls $FOO
ls: cannot access bar: No such file or directory
ls: cannot access baz: No such file or directory

Trong zshMặt khác, ba lệnh tương tự thành công. Do đó, dựa trên thí nghiệm này, dường như, trong bash, người ta có thể bỏ qua dấu ngoặc kép bên trong [[ ... ]], nhưng không phải bên trong [ ... ]cũng như trong đối số dòng lệnh, trong khi đó, trong zshdấu ngoặc kép có thể được bỏ qua trong tất cả các trường hợp này.

Nhưng suy ra các quy tắc chung từ các ví dụ giai thoại như trên là một đề xuất chancy. Sẽ thật tuyệt khi thấy một bản tóm tắt khi cần trích dẫn kép. Tôi chủ yếu quan tâm đến zsh, bash/bin/sh.


10
Hành vi quan sát của bạn trong zsh phụ thuộc vào cài đặt và bị ảnh hưởng bởi SH_WORD_SPLITtùy chọn.
Ulrich Dangel


3
Như một bên - tên biến all-caps được sử dụng bởi các biến có ý nghĩa đối với hệ điều hành và shell; đặc tả POSIX khuyên rõ ràng bằng cách sử dụng tên viết thường cho các biến do ứng dụng xác định. (Trong khi đặc tả được trích dẫn đặc biệt tập trung vào các biến môi trường, biến môi trường và biến shell chia sẻ một không gian tên: Cố gắng tạo biến shell với tên đã được sử dụng bởi biến môi trường ghi đè lên biến sau). Xem pubs.opengroup.org/onlinepub/009695399/basingefs/ , đoạn thứ tư.
Charles Duffy

Câu trả lời:


128

Đầu tiên, tách zsh khỏi phần còn lại. Đây không phải là vấn đề của vỏ cũ và hiện đại: zsh hành xử khác nhau. Các nhà thiết kế zsh đã quyết định làm cho nó không tương thích với các vỏ truyền thống (Bourne, ksh, bash), nhưng dễ sử dụng hơn.

Thứ hai, việc sử dụng dấu ngoặc kép mọi lúc sẽ dễ dàng hơn nhiều so với việc nhớ khi cần. Chúng cần thiết hầu hết thời gian, vì vậy bạn sẽ cần học khi không cần thiết, không phải khi cần.

Tóm lại, trích dẫn kép là cần thiết ở bất cứ nơi nào một danh sách các từ hoặc một mẫu được mong đợi . Chúng là tùy chọn trong các ngữ cảnh trong đó trình phân tích cú pháp dự kiến ​​sẽ có một chuỗi thô.

Điều gì xảy ra mà không có dấu ngoặc kép

Lưu ý rằng không có dấu ngoặc kép, hai điều xảy ra.

  1. Đầu tiên, kết quả của việc mở rộng (giá trị của biến cho một thay thế tham số như ${foo}, hoặc đầu ra của lệnh thay thế lệnh như thế $(foo)) được chia thành các từ bất cứ nơi nào nó chứa khoảng trắng.
    Chính xác hơn, kết quả của việc mở rộng được phân chia tại mỗi ký tự xuất hiện trong giá trị của IFSbiến (ký tự phân cách). Nếu một chuỗi các ký tự phân cách chứa khoảng trắng (dấu cách, tab hoặc dòng mới), thì khoảng trắng được tính là một ký tự đơn; phân cách không dẫn đầu, theo dõi hoặc lặp đi lặp lại dẫn đến các trường trống. Ví dụ: với IFS=" :", :one::two : three: :four tạo ra các trường trống trước one, giữa onetwovà (một cái duy nhất) giữa threefour.
  2. Mỗi trường có kết quả từ việc chia tách được hiểu là một hình cầu (một mẫu ký tự đại diện) nếu nó chứa một trong các ký tự \[*?. Nếu mẫu đó khớp với một hoặc nhiều tên tệp, mẫu đó được thay thế bằng danh sách tên tệp phù hợp.

Một mở rộng biến không được trích dẫn $foothường được gọi là toán tử chia tách + Toán tử toàn cầu, ngược lại "$foo", chỉ lấy giá trị của biến foo. Điều tương tự cũng xảy ra đối với thay thế lệnh: "$(foo)"là thay thế lệnh, $(foo)là thay thế lệnh theo sau là split + global.

Nơi bạn có thể bỏ qua dấu ngoặc kép

Dưới đây là tất cả các trường hợp tôi có thể nghĩ về trình bao kiểu Bourne nơi bạn có thể viết một biến hoặc thay thế lệnh mà không cần dấu ngoặc kép và giá trị được hiểu theo nghĩa đen.

  • Ở phía bên phải của một bài tập.

    var=$stuff
    a_single_star=*

    Lưu ý rằng bạn cần có dấu ngoặc kép sau export, bởi vì đó là một nội dung thông thường, không phải từ khóa. Điều này chỉ đúng trong một số shell như dash, zsh (trong mô phỏng sh), yash hoặc posh; bash và ksh đều đối xử exportđặc biệt.

    export VAR="$stuff"
  • Trong một casetuyên bố.

    case $var in 

    Lưu ý rằng bạn cần dấu ngoặc kép trong một mẫu trường hợp. Chia tách từ không xảy ra trong một mẫu trường hợp, nhưng một biến không được trích dẫn được hiểu là một mẫu trong khi một biến được trích dẫn được hiểu là một chuỗi bằng chữ.

    a_star='a*'
    case $var in
      "$a_star") echo "'$var' is the two characters a, *";;
       $a_star) echo "'$var' begins with a";;
    esac
  • Trong dấu ngoặc kép. Dấu ngoặc kép là cú pháp đặc biệt shell.

    [[ -e $filename ]]

    Ngoại trừ việc bạn làm cần dấu ngoặc kép mà một mô hình hoặc biểu thức chính quy dự kiến: ở phía bên tay phải của =hoặc ==hoặc !=hoặc =~.

    a_star='a*'
    if [[ $var == "$a_star" ]]; then echo "'$var' is the two characters a, *"
    elif [[ $var == $a_star ]]; then echo "'$var' begins with a"
    fi

    Bạn cần có dấu ngoặc kép như bình thường trong dấu ngoặc đơn [ … ]vì chúng là cú pháp shell thông thường (đó là một lệnh xảy ra để được gọi [). Xem dấu ngoặc đơn hoặc dấu ngoặc kép

  • Trong một chuyển hướng trong các vỏ POSIX không tương tác (không bash, cũng không ksh88).

    echo "hello world" >$filename

    Một số shell, khi tương tác, sẽ coi giá trị của biến là mẫu ký tự đại diện. POSIX nghiêm cấm hành vi đó trong các vỏ không tương tác, nhưng một số vỏ bao gồm bash (ngoại trừ ở chế độ POSIX) và ksh88 (bao gồm cả khi được tìm thấy là POSIX (được cho là) shcủa một số Unice thương mại như Solaris) vẫn thực hiện việc đó ( bashcũng cố gắng chia tách và chuyển hướng không trừ khi đó chia + globbing kết quả chính xác một từ), đó là lý do tại sao nó tốt hơn để trích dẫn mục tiêu của chuyển hướng trong một shkịch bản trong trường hợp bạn muốn chuyển đổi nó vào một bashkịch bản một ngày nào đó, hoặc chạy nó trên một hệ thống mà shlà không tuân thủ về điểm đó, hoặc nó có thể có nguồn gốc từ các vỏ tương tác.

  • Bên trong một biểu thức số học. Trong thực tế, bạn cần bỏ các dấu ngoặc kép để một biến được phân tích cú pháp dưới dạng biểu thức số học.

    expr=2*2
    echo "$(($expr))"

    Tuy nhiên, bạn không cần các trích dẫn xung quanh việc mở rộng số học vì chúng có thể bị chia tách từ trong hầu hết các shell vì POSIX yêu cầu (!?).

  • Trong một mảng con liên kết.

    typeset -A a
    i='foo bar*qux'
    a[foo\ bar\*qux]=hello
    echo "${a[$i]}"

Một biến không được trích dẫn và thay thế lệnh có thể hữu ích trong một số trường hợp hiếm gặp:

  • Khi giá trị biến hoặc đầu ra lệnh bao gồm một danh sách các mẫu hình cầu và bạn muốn mở rộng các mẫu này vào danh sách các tệp phù hợp.
  • Khi bạn biết rằng giá trị không chứa bất kỳ ký tự đại diện nào, nó $IFSkhông được sửa đổi và bạn muốn phân tách nó ở các ký tự khoảng trắng.
  • Khi bạn muốn phân tách một giá trị tại một ký tự nhất định: vô hiệu hóa toàn cầu với set -f, đặt thành IFSký tự dấu tách (hoặc để nó một mình để phân tách ở khoảng trắng), sau đó thực hiện mở rộng.

Zsh

Trong zsh, bạn có thể bỏ qua dấu ngoặc kép hầu hết các lần, với một vài ngoại lệ.

  • $varkhông bao giờ mở rộng thành nhiều từ, tuy nhiên nó sẽ mở rộng ra danh sách trống (trái ngược với danh sách chứa một từ trống, đơn) nếu giá trị của varlà chuỗi rỗng. Tương phản:

    var=
    print -l $var foo        # prints just foo
    print -l "$var" foo      # prints an empty line, then foo

    Tương tự, "${array[@]}"mở rộng cho tất cả các phần tử của mảng, trong khi $arraychỉ mở rộng thành các phần tử không trống.

  • Các @cờ mở rộng tham số đôi khi đòi hỏi dấu ngoặc kép xung quanh toàn bộ thay: "${(@)foo}".

  • Thay thế lệnh trải qua quá trình phân tách trường nếu không được trích dẫn: echo $(echo 'a'; echo '*')in a *(với một khoảng trắng) trong khi echo "$(echo 'a'; echo '*')"in chuỗi hai dòng không thay đổi. Sử dụng "$(somecommand)"để có được đầu ra của lệnh trong một từ duy nhất, sans dòng mới cuối cùng. Sử dụng "${$(somecommand; echo _)%?}"để có được đầu ra chính xác của lệnh bao gồm các dòng mới cuối cùng. Sử dụng "${(@f)$(somecommand)}"để có được một mảng các dòng từ đầu ra của lệnh.


Trong thực tế, bạn cần bỏ các dấu ngoặc kép để một biến được phân tích cú pháp dưới dạng biểu thức số học. Tại sao tôi có thể làm cho ví dụ của bạn hoạt động với dấu ngoặc kép:echo "$(("$expr"))"
Cyker

Đây là những gì man bashnói: Biểu thức được xử lý như thể nó nằm trong dấu ngoặc kép, nhưng trích dẫn kép bên trong dấu ngoặc đơn không được xử lý đặc biệt.
Cyker

4
Ngoài ra, đối với bất kỳ ai quan tâm, tên chính thức của split + globalchia tách từmở rộng tên đường dẫn .
Cyker

3
FYI - kết thúc trên StackOverflow , tôi đã có ai đó rút ngôn ngữ "tùy chọn khi một chuỗi thô được mong đợi" trong câu trả lời này để bảo vệ việc không trích dẫn một đối số echo. Có thể đáng để cố gắng làm cho ngôn ngữ trở nên rõ ràng hơn ("khi một chuỗi thô được trình phân tích cú pháp mong đợi", có lẽ?)
Charles Duffy

2
@CharlesDuffy Ugh, tôi đã không nghĩ về việc đọc sai này. Tôi đã thay đổi thành nơi mà thành một người khác thành người Hồi giáo và đã củng cố câu như bạn đề xuất.
Gilles
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.