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?
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 trả lời:
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ó $URL
cầ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 đó.
IFS=0
, sau đó echo $?
có thể rất đáng ngạc nhiên.
cp $source1 $source2 $dest
, nhưng nếu vì một lý do bất ngờ dest
không được thiết lập, số thứ ba chỉ biến mất, và nó sẽ âm thầm sao chép source1
qua source2
thay 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ố).
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).
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 $pattern
cả!)
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ư ?
và &
). 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 ls
cho 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; printf
cụ 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".
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
"StackOverflow rocks!"
, "Steve's Apple"
)"$var"
, "${arr[@]}"
)"$(ls)"
, "`ls`"
)"/my dir/"*
)"single'quote'delimited'string"
)"${filename##*/}"
)Dấu nháy đơn
'Really costs $$!'
, 'just a backslash followed by a t: \t'
)'The "crux"'
)$'\n\t'
)$'{"table": "users", "where": "first_name"=\'Steve\'}'
)Không có báo giá
$$
, $?
,$#
vv)((count++))
,"${arr[idx]}"
,"${string:start:length}"
[[ ]]
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)for word in $words
)for txtfile in *.txt; do ...
)~
được hiểu là $HOME
( ~/"some dir"
nhưng không "~/some dir"
)Xem thêm:
"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.
[[ ]]
, trích dẫn không thành vấn đề ở phía bên phải của =
/ ==
và =~
: 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.
$'...'
) chắc chắn nên có phần riêng.
"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.
case
:)
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 $var
nó không chứa không gian.
Tôi sử dụng $var
như 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
Để 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ó)