Tại sao tôi cần trích dẫn biến cho if, nhưng không phải cho echo?


26

Tôi đã đọc rằng bạn cần báo giá kép để mở rộng các biến, ví dụ:

if [ -n "$test" ]; then echo '$test ok'; else echo '$test null'; fi

sẽ làm việc như mong đợi, trong khi

if [ -n $test ]; then echo '$test ok'; else echo '$test null'; fi

sẽ luôn luôn nói $test okngay cả khi $testlà null.

Nhưng tại sao chúng ta không cần trích dẫn echo $test?


2
Nếu bạn không trích dẫn một biến để sử dụng làm đối số echo, các khoảng trắng và dòng mới sẽ bị xóa.
jordanm

Câu trả lời:


36

Bạn luôn cần các trích dẫn xung quanh các biến trong tất cả các bối cảnh danh sách , đó là ở mọi nơi, biến có thể được mở rộng thành nhiều giá trị trừ khi bạn muốn 3 tác dụng phụ của việc không để lại một biến.

danh sách bối cảnh bao gồm các đối số cho các lệnh đơn giản như [hoặc echo, các for i in <here>phép gán cho mảng ... Có các bối cảnh khác trong đó các biến cũng cần được trích dẫn. Tốt nhất là luôn luôn trích dẫn các biến trừ khi bạn có lý do rất chính đáng để không.

Hãy nghĩ về sự vắng mặt của dấu ngoặc kép (trong ngữ cảnh danh sách) là toán tử split + global .

Như thể echo $testecho glob(split("$test")).

Hành vi shell gây nhầm lẫn cho hầu hết mọi người bởi vì trong hầu hết các ngôn ngữ khác, bạn đặt dấu ngoặc kép quanh các chuỗi cố định, như puts("foo"), chứ không phải xung quanh các biến (như puts(var)) trong khi vỏ lại theo cách khác: mọi thứ đều là chuỗi, vì vậy hãy đặt dấu ngoặc kép xung quanh mọi thứ sẽ là cồng kềnh, bạn echo test, bạn không cần "echo" "test". Trong shell, dấu ngoặc kép được sử dụng cho mục đích khác: ngăn chặn một số ý nghĩa đặc biệt của một số ký tự và / hoặc ảnh hưởng đến hành vi của một số mở rộng.

Trong [ -n $test ]hoặc echo $test, shell sẽ phân tách $test(theo khoảng trống theo mặc định) và sau đó thực hiện việc tạo tên tệp (mở rộng tất cả các mẫu *, '?' ... vào danh sách các tệp phù hợp), sau đó chuyển danh sách đối số đó cho các lệnh [hoặc echolệnh .

Một lần nữa, hãy nghĩ về nó như "[" "-n" glob(split("$test")) "]". Nếu $testlà trống hoặc chỉ chứa khoảng trống (SPC, tab, nl), sau đó các nhà điều hành phân chia + glob sẽ trả về một danh sách trống, vì vậy [ -n $test ]sẽ "[" "-n" "]", đó là một thử nghiệm để kiểm tra wheter "-n" là chuỗi rỗng hay không. Nhưng hãy tưởng tượng những gì sẽ xảy ra nếu $testlà "*" hoặc "= foo" ...

Trong [ -n "$test" ], [được thông qua bốn đối số "[", "-n", """]"(không có dấu ngoặc kép), đó là những gì chúng ta muốn.

Cho dù đó là echohoặc [không có sự khác biệt, chỉ là nó tạo echora điều tương tự cho dù nó có thông qua một đối số trống hay không có đối số nào.

Xem thêm câu trả lời này cho một câu hỏi tương tự để biết thêm chi tiết về [lệnh và [[...]]cấu trúc.


7

Câu trả lời của @ h3rreller là tốt để giải thích lý do tại sao bạn cần trích dẫn cho if(hoặc đúng hơn, [/ test), nhưng tôi thực sự sẽ đặt ra rằng câu hỏi của bạn không chính xác.

Hãy thử các lệnh sau, và bạn sẽ thấy những gì tôi muốn nói.

export testvar="123    456"
echo $testvar
echo "$testvar"

Không có dấu ngoặc kép, thay thế biến làm cho lệnh thứ hai mở rộng thành:

echo 123    456

và nhiều không gian được thu gọn thành một không gian duy nhất:

echo 123 456

Với các trích dẫn, các không gian được bảo tồn.

Điều này xảy ra bởi vì khi bạn trích dẫn một tham số (cho dù là tham số được truyền cho echo, testhoặc một số lệnh khác), giá trị của tham số được gửi như một giá trị cho các lệnh. Nếu bạn không trích dẫn nó, shell sẽ thực hiện phép thuật thông thường là tìm khoảng trắng để xác định nơi mỗi tham số bắt đầu và kết thúc.

Điều này cũng có thể được minh họa bằng chương trình C sau (rất rất đơn giản). Hãy thử làm như sau trên dòng lệnh (bạn có thể muốn thực hiện nó trong một thư mục trống để không mạo hiểm ghi đè lên một cái gì đó).

cat <<EOF >paramtest.c
#include <stdio.h>
int main(int argc, char **argv) {
  int nparams = argc-1; /* because 1 parameter means only the executable's name */
  printf("%d parameters received\n", nparams);
  return nparams;
}
EOF
cc -o paramtest paramtest.c

và sau đó...

./paramtest 123 456
./paramtest "123 456"
./paramtest 123   456
./paramtest "123   456"

Sau khi chạy paramtest, $?sẽ giữ số lượng tham số đã được thông qua (và số đó sẽ được in).


2

Đây là tất cả về cách shell diễn giải dòng trước khi chương trình được thực thi.

Nếu dòng này đọc echo I am $USER, shell sẽ mở rộng nó ra echo I am blrflechokhông có manh mối cho dù nguồn gốc của văn bản là mở rộng theo nghĩa đen hay biến đổi. Tương tự, nếu một dòng đọc echo I am $UNDEFINED, shell sẽ mở rộng $UNDEFINEDthành không có gì và các đối số của echo sẽ là I am, và đó là kết thúc của nó. Vì echohoạt động tốt chỉ không có đối số, echo $UNDEFINEDlà hoàn toàn hợp lệ.

Vấn đề của bạn ifkhông thực sự xảy ra if, bởi vì ifchỉ chạy bất kỳ chương trình nào và các đối số tuân theo nó và thực thi thenphần đó nếu chương trình thoát 0(hoặc elsephần nếu có một và chương trình không thoát 0):

if /bin/true ; then echo True dat. ; fi
if fgrep -q blrfl /etc/passwd ; then echo Blrfl has an account. ; fi

Khi bạn sử dụng if [ ... ]để so sánh, bạn không sử dụng các nguyên hàm được tích hợp vào vỏ. Bạn đang thực sự hướng dẫn shell để chạy một chương trình được gọi [là siêu thay thế rất nhỏ trong test(1)đó yêu cầu đối số cuối cùng của nó ]. Cả hai chương trình đều thoát 0nếu điều kiện kiểm tra trở thành đúng và 1nếu không.

Lý do một số bài kiểm tra bị phá vỡ khi một biến không được xác định là vì testkhông thấy rằng bạn đang sử dụng một biến. Ergo, [ $UNDEFINED -eq 2 ]phá vỡ bởi vì vào thời điểm shell được thực hiện với nó, tất cả các lần testxem đối số là -eq 2 ], đó không phải là một thử nghiệm hợp lệ. Nếu bạn đã làm nó với một cái gì đó được xác định, chẳng hạn như [ $DEFINED -ne 0 ], nó sẽ hoạt động vì shell sẽ mở rộng nó thành một thử nghiệm hợp lệ (ví dụ 0 -ne 0:).

Có một sự khác biệt về ngữ nghĩa giữa foo $UNDEFINED bar, mở rộng thành hai đối số ( foobar) vì $UNDEFINEDsống đúng với tên của nó. So sánh điều này với foo "$UNDEFINED" bar, mở rộng thành ba đối số ( foo, một chuỗi rỗng và `bar). Các trích dẫn buộc shell phải diễn giải chúng như một cuộc tranh luận cho dù có bất cứ điều gì giữa chúng hay không.


0

Không có dấu ngoặc kép $testcó thể mở rộng thành nhiều hơn một từ nên nó cần được trích dẫn để không phá vỡ cú pháp vì mỗi chuyển đổi bên trong [lệnh đang mong đợi một đối số là những gì trích dẫn làm (làm cho bất cứ điều gì $testmở rộng thành một đối số)

Lý do bạn không cần dấu ngoặc kép để mở rộng một biến echolà vì nó không mong đợi một đối số. Nó chỉ đơn giản là in những gì bạn nói với nó. Vì vậy, ngay cả khi $testmở rộng đến 100 từ echo vẫn sẽ in nó.

Hãy nhìn vào Cạm bẫy Bash


đúng nhưng tại sao chúng ta không cần nó echo?
CharlesB

@CharlesB Bạn cần báo giá cho echo. Điều gì khiến bạn nghĩ khác?
Gilles 'SO- ngừng trở nên xấu xa'

Tôi không cần chúng, tôi có thể echo $testvà nó hoạt động (nó tạo ra giá trị của $ test)
CharlesB

1
@CharlesB Nó chỉ xuất giá trị của $ test nếu không chứa nhiều khoảng trắng ở bất cứ đâu. Hãy thử chương trình trong câu trả lời của tôi cho một minh họa về lý do.
một CVn

0

Các tham số trống sẽ bị xóa nếu không được trích dẫn:

start cmd:> strace -e trace=execve echo foo $bar baz
execve("/usr/bin/echo", ["echo", "foo", "baz"], [/* 100 vars */]) = 0

start cmd:> strace -e trace=execve echo foo "$bar" baz
execve("/usr/bin/echo", ["echo", "foo", "", "baz"], [/* 100 vars */]) = 0

Lệnh được gọi không thấy rằng có một tham số trống trên dòng lệnh shell. Có vẻ như [được định nghĩa trả về 0 cho -n mà không có gì theo sau. Sao cũng được.

Trích dẫn cũng tạo ra sự khác biệt cho tiếng vang, trong một số trường hợp:

var='*'
echo $var
echo "$var"

var="foo        bar"
echo $var
echo "$var"

2
Không phải echo, đó là vỏ. Bạn sẽ thấy hành vi tương tự với ls. Hãy thử touch '*'một thời gian nếu bạn cảm thấy phiêu lưu. :)
một CVn

Đó chỉ là cách diễn đạt vì không có sự khác biệt nào với trường hợp 'nếu [...] `. [không phải là một lệnh shell đặc biệt. Điều đó khác với [[(trong bash) khi không cần trích dẫn.
Hauke ​​Laging
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.