Khi nào cần bọc dấu ngoặc kép quanh một biến shell?


184

Ai đó có thể cho tôi biết liệu tôi có nên bọc dấu ngoặc kép quanh các biến trong tập lệnh shell không?

Ví dụ, là đúng sau đây:

xdg-open $URL 
[ $? -eq 2 ]

hoặc là

xdg-open "$URL"
[ "$?" -eq "2" ]

Và nếu vậy, tại sao?



Câu hỏi này nhận được rất nhiều sự trùng lặp, nhiều trong số đó không phải là về các biến, vì vậy tôi đã đổi tên thành "giá trị" thay vì "biến". Tôi hy vọng điều này sẽ giúp nhiều người tìm thấy chủ đề này.
tripleee 28/03/2017

1
@codeforester Điều gì xảy ra với chỉnh sửa hoàn nguyên?
tripleee


Câu trả lời:


131

Quy tắc chung: trích dẫn nó nếu nó có thể trống hoặc chứa khoảng trắng (hoặc bất kỳ khoảng trắng nào thực sự) hoặc ký tự đặc biệt (ký tự đại diện). Không trích dẫn các chuỗi có khoảng trắng thường dẫn đến trình bao tách rời một đối số thành nhiều đối số.

$?không cần dấu ngoặc kép vì nó là một giá trị số. Việc có $URLcần hay không phụ thuộc vào những gì bạn cho phép trong đó và liệu bạn có muốn tranh luận hay không nếu nó trống.

Tôi có xu hướng luôn trích dẫn các chuỗi theo thói quen vì nó an toàn hơn theo cách đó.


2
Lưu ý rằng "khoảng trắng" thực sự có nghĩa là "bất kỳ khoảng trắng".
William Pursell

4
@Cristian: Nếu bạn không chắc chắn những gì có thể có trong biến, thì an toàn hơn để trích dẫn nó. Tôi có xu hướng tuân theo nguyên tắc tương tự như paxdiablo, và chỉ tạo thói quen trích dẫn mọi thứ (trừ khi có lý do cụ thể không).
Gordon Davisson

11
Nếu bạn không biết giá trị của IFS, hãy trích dẫn nó bất kể là gì. Nếu IFS=0, sau đó echo $?có thể rất đáng ngạc nhiên.
Charles Duffy

3
Trích dẫn dựa trên bối cảnh, không dựa trên những gì bạn mong đợi các giá trị, nếu không các lỗi của bạn sẽ tồi tệ hơn. Ví dụ, bạn chắc chắn rằng không ai trong số những con đường của bạn có các khoảng trống, vì vậy bạn nghĩ bạn có thể viết cp $source1 $source2 $dest, nhưng nếu vì một lý do bất ngờ destkhông được thiết lập, số thứ ba chỉ biến mất, và nó sẽ âm thầm sao chép source1qua source2thay vì đem lại cho bạn một lỗi thích hợp cho đích trống (như sẽ có nếu bạn đã trích dẫn từng đối số).
Derek Veit

3
quote it if...có quá trình suy nghĩ ngược - trích dẫn không phải là thứ bạn thêm vào khi bạn cần, chúng là thứ bạn xóa khi bạn cần. Luôn bao bọc các chuỗi và tập lệnh trong các dấu ngoặc đơn trừ khi bạn cần sử dụng dấu ngoặc kép (ví dụ: để cho phép một biến mở rộng) hoặc không cần sử dụng dấu ngoặc kép (ví dụ: để mở rộng toàn cầu và mở rộng tên tệp).
Ed Morton

92

Nói tóm lại, hãy trích dẫn mọi thứ mà bạn không yêu cầu shell để thực hiện phân tách mã thông báo và mở rộng ký tự đại diện.

Trích dẫn duy nhất bảo vệ văn bản giữa chúng nguyên văn. Đây là công cụ thích hợp khi bạn cần đảm bảo rằng vỏ hoàn toàn không chạm vào chuỗi. Thông thường, nó là cơ chế trích dẫn của sự lựa chọn khi bạn không yêu cầu nội suy biến.

$ echo 'Nothing \t in here $will change'
Nothing \t in here $will change

$ grep -F '@&$*!!' file /dev/null
file:I can't get this @&$*!! quoting right.

Dấu ngoặc kép là phù hợp khi yêu cầu nội suy biến. Với các điều chỉnh phù hợp, nó cũng là một cách giải quyết tốt khi bạn cần các dấu ngoặc đơn trong chuỗi. (Không có cách đơn giản nào để thoát một trích dẫn giữa các trích dẫn đơn, bởi vì không có cơ chế thoát bên trong các trích dẫn đơn - nếu có, chúng sẽ không trích dẫn hoàn toàn nguyên văn.)

$ echo "There is no place like '$HOME'"
There is no place like '/home/me'

Không có trích dẫn nào phù hợp khi bạn đặc biệt yêu cầu trình bao để thực hiện phân tách mã thông báo và / hoặc mở rộng ký tự đại diện.

Chia tách mã thông báo;

 $ words="foo bar baz"
 $ for word in $words; do
 >   echo "$word"
 > done
 foo
 bar
 baz

Ngược lại:

 $ for word in "$words"; do echo "$word"; done
 foo bar baz

(Vòng lặp chỉ chạy một lần, trên chuỗi đơn, được trích dẫn.)

 $ for word in '$words'; do echo "$word"; done
 $words

(Vòng lặp chỉ chạy một lần, qua chuỗi trích dẫn đơn bằng chữ.)

Mở rộng ký tự đại diện:

$ pattern='file*.txt'
$ ls $pattern
file1.txt      file_other.txt

Ngược lại:

$ ls "$pattern"
ls: cannot access file*.txt: No such file or directory

(Không có tệp có tên theo nghĩa đen file*.txt.)

$ ls '$pattern'
ls: cannot access $pattern: No such file or directory

(Không có tệp nào được đặt tên $patterncả!)

Nói một cách cụ thể hơn, mọi thứ có chứa tên tệp thường được trích dẫn (vì tên tệp có thể chứa khoảng trắng và các siêu ký tự shell khác). Bất cứ điều gì có chứa một URL thường nên được trích dẫn (vì nhiều URL chứa các siêu ký tự shell như ?&). Bất cứ điều gì có chứa một regex thường nên được trích dẫn (ditto ditto). Bất cứ điều gì có chứa khoảng trắng đáng kể ngoài khoảng trắng đơn giữa các ký tự không phải khoảng trắng cần được trích dẫn (bởi vì nếu không, shell sẽ chuyển khoảng trắng vào, một cách hiệu quả, các khoảng trắng đơn và cắt bất kỳ khoảng trắng đầu hoặc cuối nào).

Khi bạn biết rằng một biến chỉ có thể chứa một giá trị không chứa siêu ký tự shell, trích dẫn là tùy chọn. Do đó, một không được trích dẫn $?về cơ bản là tốt, bởi vì biến này chỉ có thể chứa một số duy nhất. Tuy nhiên, "$?"cũng đúng và được khuyến nghị về tính nhất quán và tính chính xác chung (mặc dù đây là khuyến nghị cá nhân của tôi, không phải là một chính sách được công nhận rộng rãi).

Các giá trị không phải là các biến về cơ bản tuân theo các quy tắc tương tự, mặc dù sau đó bạn cũng có thể thoát khỏi bất kỳ ký tự đại diện nào thay vì trích dẫn chúng. Đối với một ví dụ phổ biến, một URL có dấu &trong đó sẽ được phân tách bằng shell dưới dạng lệnh nền trừ khi metacharacter được thoát hoặc trích dẫn:

$ wget http://example.com/q&uack
[1] wget http://example.com/q
-bash: uack: command not found

(Tất nhiên, điều này cũng xảy ra nếu URL nằm trong một biến không được trích dẫn.) Đối với một chuỗi tĩnh, các trích dẫn đơn có ý nghĩa nhất, mặc dù bất kỳ hình thức trích dẫn hoặc thoát nào đều hoạt động ở đây.

wget 'http://example.com/q&uack'  # Single quotes preferred for a static string
wget "http://example.com/q&uack"  # Double quotes work here, too (no $ or ` in the value)
wget http://example.com/q\&uack   # Backslash escape
wget http://example.com/q'&'uack  # Only the metacharacter really needs quoting

Ví dụ cuối cùng cũng gợi ý một khái niệm hữu ích khác, mà tôi muốn gọi là "trích dẫn bập bênh". Nếu bạn cần kết hợp dấu ngoặc đơn và dấu ngoặc kép, bạn có thể sử dụng chúng liền kề nhau. Ví dụ: các chuỗi trích dẫn sau đây

'$HOME '
"isn't"
' where `<3'
"' is."

có thể được dán cùng nhau trở lại, tạo thành một chuỗi dài sau khi mã hóa và loại bỏ trích dẫn.

$ echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.

Đây không phải là quá dễ đọc, nhưng nó là một kỹ thuật phổ biến và do đó tốt để biết.

Như một bên, kịch bản thường không nên sử dụng lscho bất cứ điều gì. Để mở rộng ký tự đại diện, chỉ cần ... sử dụng nó.

$ printf '%s\n' $pattern   # not ``ls -1 $pattern''
file1.txt
file_other.txt

$ for file in $pattern; do  # definitely, definitely not ``for file in $(ls $pattern)''
>  printf 'Found file: %s\n' "$file"
> done
Found file: file1.txt
Found file: file_other.txt

(Vòng lặp hoàn toàn không cần thiết trong ví dụ sau; printfcụ thể cũng hoạt động tốt với nhiều đối số stat. Nhưng việc lặp qua khớp ký tự đại diện là một vấn đề phổ biến và thường được thực hiện không chính xác.)

Một biến chứa danh sách các mã thông báo lặp lại hoặc ký tự đại diện để mở rộng ít thấy hơn, vì vậy đôi khi chúng tôi viết tắt để "trích dẫn mọi thứ trừ khi bạn biết chính xác những gì bạn đang làm".


1
Đây là một biến thể của (một phần) câu trả lời tôi đã đăng cho một câu hỏi liên quan . Tôi dán nó ở đây bởi vì điều này cô đọng và đủ rõ ràng để trở thành một câu hỏi kinh điển cho vấn đề đặc biệt này.
tripleee 30/12/14

4
Tôi sẽ lưu ý rằng đây là mục số 0 và một chủ đề định kỳ trên bộ sưu tập mywiki.wooledge.org/BashPit thác về các lỗi Bash phổ biến. Nhiều, rất nhiều mục cá nhân trong danh sách đó về cơ bản là về vấn đề này.
tripleee

27

Dưới đây là một công thức ba điểm để trích dẫn nói chung:

Dấu ngoặc kép

Trong bối cảnh mà chúng tôi muốn ngăn chặn việc chia tách và nối từ. Ngoài ra trong bối cảnh mà chúng tôi muốn nghĩa đen được coi là một chuỗi, không phải là một biểu thức chính quy.

Dấu nháy đơn

Trong chuỗi ký tự, nơi chúng tôi muốn triệt tiêu nội suy và xử lý đặc biệt các dấu gạch chéo ngược. Nói cách khác, các tình huống sử dụng dấu ngoặc kép sẽ không phù hợp.

Không có báo giá

Trong bối cảnh nơi chúng tôi hoàn toàn chắc chắn rằng không có vấn đề chia tách hoặc tách từ hoặc chúng tôi muốn chia tách từ và toàn cầu hóa .


Ví dụ

Dấu ngoặc kép

  • chuỗi ký tự có khoảng trắng ( "StackOverflow rocks!", "Steve's Apple")
  • mở rộng biến ( "$var", "${arr[@]}")
  • thay thế lệnh ( "$(ls)", "`ls`")
  • quả cầu nơi đường dẫn thư mục hoặc phần tên tệp bao gồm khoảng trắng ( "/my dir/"*)
  • để bảo vệ dấu ngoặc đơn ( "single'quote'delimited'string")
  • Mở rộng tham số Bash ( "${filename##*/}")

Dấu nháy đơn

  • tên lệnh và đối số có khoảng trắng trong chúng
  • chuỗi ký tự cần nội suy bị triệt tiêu ( 'Really costs $$!', 'just a backslash followed by a t: \t')
  • để bảo vệ dấu ngoặc kép ( 'The "crux"')
  • regex chữ cần nội suy để bị loại bỏ
  • sử dụng trích dẫn shell cho các chữ có liên quan đến các ký tự đặc biệt ($'\n\t' )
  • sử dụng trích dẫn shell nơi chúng ta cần bảo vệ một số dấu ngoặc đơn và dấu ngoặc kép ( $'{"table": "users", "where": "first_name"=\'Steve\'}')

Không có báo giá

  • biến số xung quanh tiêu chuẩn ( $$, $?,$# vv)
  • trong bối cảnh số học như ((count++)),"${arr[idx]}" ,"${string:start:length}"
  • phía trong [[ ]] biểu hiện không có vấn đề phân tách từ ngữ và toàn cầu hóa (đây là vấn đề về phong cách và ý kiến ​​có thể thay đổi rộng rãi)
  • nơi chúng tôi muốn tách từ (for word in $words )
  • nơi chúng tôi muốn globalbing ( for txtfile in *.txt; do ...)
  • nơi chúng tôi muốn ~được hiểu là $HOME( ~/"some dir"nhưng không "~/some dir")

Xem thêm:


3
Theo các hướng dẫn này, người ta sẽ có được một danh sách các tệp trong thư mục gốc bằng cách viết "ls" "/" Cụm từ "tất cả các bối cảnh chuỗi" cần phải đủ điều kiện cẩn thận hơn.
William Pursell

5
Trong [[ ]], trích dẫn không thành vấn đề ở phía bên phải của =/ ===~: nó tạo ra sự khác biệt giữa việc diễn giải một chuỗi dưới dạng mẫu / regex hoặc theo nghĩa đen.
Benjamin W.

6
Một tổng quan tốt, nhưng các nhận xét của @ BenjaminW rất đáng để tích hợp và các chuỗi được trích dẫn ANSI C ( $'...') chắc chắn nên có phần riêng.
mkuity0

3
@ mkuity0, thực sự chúng là tương đương. Các hướng dẫn này chỉ ra rằng bạn phải luôn luôn gõ "ls" "/"thay vì phổ biến hơn ls /và tôi coi đó là một lỗ hổng lớn trong hướng dẫn.
William Pursell

4
Để không có dấu ngoặc kép, bạn có thể thêm phép gán biến hoặc case:)
PesaThe

4

Tôi thường sử dụng trích dẫn như thế nào "$var"cho an toàn, trừ khi tôi chắc chắn rằng $varnó không chứa không gian.

Tôi sử dụng $varnhư một cách đơn giản để tham gia các dòng:

lines="`cat multi-lines-text-file.txt`"
echo "$lines"                             ## multiple lines
echo $lines                               ## all spaces (including newlines) are zapped

Nhận xét cuối cùng có phần sai lệch; các dòng mới được thay thế một cách hiệu quả bằng các khoảng trắng, không chỉ đơn giản là loại bỏ.
tripleee

-1

Để sử dụng các biến trong tập lệnh shell, sử dụng "" các biến được trích dẫn là một biến được trích dẫn có nghĩa là biến đó có thể chứa khoảng trắng hoặc ký tự đặc biệt sẽ không ảnh hưởng đến việc thực thi tập lệnh shell của bạn. Khác nếu bạn chắc chắn không có bất kỳ khoảng trắng hoặc ký tự đặc biệt nào trong tên biến của mình thì bạn có thể sử dụng chúng mà không có "".

Thí dụ:

echo "$ url name" - (Có thể được sử dụng mọi lúc)

echo "$ url name" - (Không thể được sử dụng trong các tình huống như vậy, vì vậy hãy thận trọng trước khi sử dụng 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.