Làm cách nào để thoát ký tự đại diện / dấu hoa thị trong bash?


136

Ví dụ:

me$ FOO="BAR * BAR"
me$ echo $FOO
BAR file1 file2 file3 file4 BAR

và sử dụng \ký tự thoát:

me$ FOO="BAR \* BAR"
me$ echo $FOO
BAR \* BAR

Tôi rõ ràng đang làm điều gì đó ngu ngốc.

Làm thế nào để tôi có được đầu ra BAR * BAR?

Câu trả lời:


139

Trích dẫn khi cài đặt $FOOlà không đủ. Bạn cũng cần trích dẫn tham chiếu biến:

me$ FOO="BAR * BAR"
me$ echo "$FOO"
BAR * BAR

8
Điều này là bí ẩn, tại sao lại thế này? chuyện gì đang xảy ra vậy
tofutim

Bởi vì các biến mở rộng
Daniel

103

CÂU TRẢ LỜI NGẮN

Giống như những người khác đã nói - bạn nên luôn trích dẫn các biến để ngăn chặn hành vi lạ. Vì vậy, hãy sử dụng echo "$ foo" thay vì chỉ echo $ foo .

CÂU TRẢ LỜI DÀI

Tôi nghĩ rằng ví dụ này đảm bảo giải thích thêm bởi vì có nhiều điều đang diễn ra hơn là nó có vẻ như trên khuôn mặt của nó.

Tôi có thể thấy sự nhầm lẫn của bạn đến từ đâu bởi vì sau khi bạn chạy ví dụ đầu tiên, bạn có thể tự nghĩ rằng cái vỏ rõ ràng đang làm:

  1. Mở rộng tham số
  2. Mở rộng tên tệp

Vì vậy, từ ví dụ đầu tiên của bạn:

me$ FOO="BAR * BAR"
me$ echo $FOO

Sau khi mở rộng tham số tương đương với:

me$ echo BAR * BAR

Và sau khi mở rộng tên tệp tương đương với:

me$ echo BAR file1 file2 file3 file4 BAR

Và nếu bạn chỉ cần gõ echo BAR * BARvào dòng lệnh bạn sẽ thấy rằng chúng là tương đương.

Vì vậy, bạn có thể tự nghĩ "nếu tôi thoát khỏi *, tôi có thể ngăn chặn việc mở rộng tên tệp"

Vì vậy, từ ví dụ thứ hai của bạn:

me$ FOO="BAR \* BAR"
me$ echo $FOO

Sau khi mở rộng tham số nên tương đương với:

me$ echo BAR \* BAR

Và sau khi mở rộng tên tệp nên tương đương với:

me$ echo BAR \* BAR

Và nếu bạn thử gõ "echo BAR \ * BAR" trực tiếp vào dòng lệnh, nó thực sự sẽ in "BAR * BAR" vì việc mở rộng tên tệp bị ngăn chặn bởi lối thoát.

Vậy tại sao sử dụng $ foo không hoạt động?

Đó là bởi vì có một bản mở rộng thứ ba diễn ra - Trích dẫn Loại bỏ. Từ bash thủ công loại bỏ trích dẫn là:

Sau các lần mở rộng trước đó, tất cả các lần xuất hiện không được trích dẫn của các ký tự '\', '' 'và' "'không xuất phát từ một trong các lần mở rộng trên đều bị xóa.

Vì vậy, điều xảy ra là khi bạn nhập lệnh trực tiếp vào dòng lệnh, ký tự thoát không phải là kết quả của lần mở rộng trước đó, vì vậy BASH sẽ xóa nó trước khi gửi nó tới lệnh echo, nhưng trong ví dụ thứ 2, "\ *" là kết quả của việc mở rộng Thông số trước đó, vì vậy nó KHÔNG bị xóa. Kết quả là echo nhận được "\ *" và đó là những gì nó in.

Lưu ý sự khác biệt giữa ví dụ đầu tiên - "*" không được bao gồm trong các ký tự sẽ bị xóa bằng Trích dẫn loại bỏ.

Tôi hy vọng điều này có ý nghĩa. Cuối cùng, kết luận giống nhau - chỉ cần sử dụng dấu ngoặc kép. Tôi chỉ nghĩ rằng tôi sẽ giải thích lý do tại sao thoát, mà logic sẽ hoạt động nếu chỉ mở rộng Parameter và Filename, không hoạt động.

Để được giải thích đầy đủ về việc mở rộng BASH, hãy tham khảo:

http://www.gnu.org/software/bash/manual/bashref.html#Shell-Expansions


1
Câu trả lời chính xác! Bây giờ tôi không cảm thấy tôi đã hỏi một câu hỏi ngớ ngẩn như vậy. :-)
andyuk

1
Có tồn tại một số tiện ích để thoát khỏi các nhân vật đặc biệt?
Jigar Joshi

"Bạn nên luôn luôn trích dẫn các biến để ngăn chặn hành vi lạ" - khi bạn muốn sử dụng chúng dưới dạng chuỗi
Angelo


Mặc dù câu trả lời từ @finnw ở trên không trả lời trực tiếp câu hỏi, nhưng câu trả lời này tốt hơn nhiều trong việc giải thích lý do tại sao , điều này giúp ích nhiều hơn trong việc ngoại suy cách sử dụng trong các tình huống của chúng ta sẽ hoạt động. Đây là loại câu trả lời chúng ta cần xem thêm.
Nicolas Coombs

54

Tôi sẽ thêm một chút vào chủ đề cũ này.

Thông thường bạn sẽ sử dụng

$ echo "$FOO"

Tuy nhiên, tôi đã gặp vấn đề ngay cả với cú pháp này. Hãy xem xét các kịch bản sau đây.

#!/bin/bash
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"

Các *nhu cầu được thông qua nguyên văn curl, nhưng những vấn đề tương tự sẽ phát sinh. Ví dụ trên sẽ không hoạt động (nó sẽ mở rộng thành tên tệp trong thư mục hiện tại) và cả sẽ không \*. Bạn cũng không thể trích dẫn $curl_optsvì nó sẽ được công nhận là một tùy chọn (không hợp lệ) curl.

curl: option -s --noproxy * -O: is unknown
curl: try 'curl --help' or 'curl --manual' for more information

Do đó, tôi khuyên bạn nên sử dụng bashbiến $GLOBIGNOREđể ngăn chặn việc mở rộng tên tệp hoàn toàn nếu được áp dụng cho mẫu toàn cục hoặc sử dụng set -fcờ tích hợp.

#!/bin/bash
GLOBIGNORE="*"
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"  ## no filename expansion

Áp dụng cho ví dụ ban đầu của bạn:

me$ FOO="BAR * BAR"

me$ echo $FOO
BAR file1 file2 file3 file4 BAR

me$ set -f
me$ echo $FOO
BAR * BAR

me$ set +f
me$ GLOBIGNORE=*
me$ echo $FOO
BAR * BAR

4
Giải thích tuyệt vời, cảm ơn! Usecase của tôi là SELECT * FROM etc., đây là cách duy nhất hoạt động.
knutole 18/07/2015

1
Cảm ơn đã trình bày giải pháp set -f!
hachre

5
FOO='BAR * BAR'
echo "$FOO"

Điều này hoạt động, nhưng không nhất thiết phải thay đổi dấu ngoặc đơn trên dòng đầu tiên thành dấu ngoặc kép.
vây

1
Không, không, nhưng thói quen sử dụng dấu ngoặc đơn là thích hợp hơn khi bạn sẽ bao gồm các ký tự shell đặc biệt và bạn không muốn bất kỳ thay thế nào.
tzot


3

Có thể đáng để tập thói quen sử dụng printfthay vì echotrên dòng lệnh.

Trong ví dụ này, nó không mang lại nhiều lợi ích nhưng nó có thể hữu ích hơn với đầu ra phức tạp hơn.

FOO="BAR * BAR"
printf %s "$FOO"

Tại sao lại ở địa ngục? printf là một quy trình riêng biệt (ít nhất, không phải là bash tích hợp) và việc sử dụng printf mà bạn chứng minh không có lợi ích gì đối với tiếng vang.
ddaa

2
printf một bash tích hợp và tôi đã nói trong câu trả lời của mình "Trong ví dụ này nó không mang lại nhiều lợi ích nhưng nó có thể hữu ích hơn với đầu ra phức tạp hơn.
Dave Webb
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.