Cách đúng để trích dẫn $ (lệnh $ arg) là gì?


17

Đã đến lúc phải giải quyết câu hỏi hóc búa này đã làm phiền tôi trong nhiều năm ...

Thỉnh thoảng tôi đã gặp điều này và nghĩ rằng đây là cách để đi:

$(comm "$(arg)")

Và nghĩ rằng quan điểm của tôi được hỗ trợ mạnh mẽ bởi kinh nghiệm. Nhưng tôi không chắc nữa. Shellcheck cũng không thể quyết định được. Đó là cả hai:

"$(dirname $0)"/stop.bash
           ^-- SC2086: Double quote to prevent globbing and word splitting.

Và:

$(dirname "$0")/stop.bash
^-- SC2046: Quote this to prevent word splitting.

Logic đằng sau là gì?

(Đó là phiên bản Shellcheck 0.4.4, btw.)


1
Trích dẫn tất cả các thay thế.
Ignacio Vazquez-Abrams

3
Chỉ như thế này : "$(dirname "$0")"/stop.bash? Nó dường như hoạt động ... Câu chuyện là gì?
Tomasz

1
bash đủ thông minh để biết khi nào bối cảnh đã thay đổi.
Ignacio Vazquez-Abrams

1
Nghĩ về nó như thế này :: shell_level_1 $(shell_level_2) shell_level_1khi bạn ở trong " $(....)bạn nhập" lớp vỏ "của vỏ, NHƯNG bạn có thể viết vào đó như thể bạn ở cấp độ chính (nghĩa là bạn có thể viết trực tiếp "thay vì \", v.v.). ví dụ: touch "/tmp/a file" ; echo "its size is: $(find "/tmp/a file" -ls | awk '{print $5}) ..." : if you used backticks you'd have to find \ "/ tmp / một tệp \" `và print \$5. Với $(...)không cần: sự thích nghi vỏ đến tầm cao mới và bạn có thể viết trực tiếp như thể thông dịch viên của bạn bây giờ là ở cấp độ đó quá.
Olivier Dulac

"Shellcheck cũng không thể quyết định được." - Hai lần chạy có hai thông điệp khác nhau và chỉ vào những nơi khác nhau. Nó muốn cả hai.
Izkata

Câu trả lời:


45

Bạn cần sử dụng "$(somecmd "$file")".

Nếu không có dấu ngoặc kép, một đường dẫn có khoảng trắng sẽ được phân chia trong đối số somecmdvà nó sẽ nhắm mục tiêu tệp sai. Vì vậy, bạn cần báo giá ở bên trong.

Bất kỳ khoảng trắng nào trong đầu ra của somecmdcũng sẽ gây ra sự phân tách, vì vậy bạn cần dấu ngoặc kép ở bên ngoài toàn bộ thay thế lệnh.

Các trích dẫn bên trong thay thế lệnh không có tác dụng đối với các trích dẫn bên ngoài nó. Hướng dẫn tham khảo riêng của Bash không quá rõ ràng về điều này, nhưng BashGuide đề cập rõ ràng về nó . Văn bản trong POSIX cũng yêu cầu nó, vì "mọi tập lệnh shell hợp lệ" đều được phép bên trong $(...):

Với $(command)biểu mẫu, tất cả các ký tự theo dấu ngoặc đơn mở cho dấu ngoặc đơn đóng phù hợp sẽ tạo thành lệnh. Bất kỳ tập lệnh shell hợp lệ nào cũng có thể được sử dụng cho lệnh, ngoại trừ tập lệnh chỉ bao gồm các chuyển hướng tạo ra kết quả không xác định.


Thí dụ:

$ file="./space here/foo"

a. Không có trích dẫn, dirnamexử lý cả ./spacehere/foo:

$ printf "<%s>\n" $(dirname $file)
<.>
<here>

b. Báo giá bên trong, dirnamequy trình ./space here/foo, cho ./space here, được chia làm hai:

$ printf "<%s>\n" $(dirname "$file")
<./space>
<here>

c. Báo giá bên ngoài, dirnamexử lý cả hai ./spacehere/foo, đầu ra trên các dòng riêng biệt, nhưng bây giờ hai dòng tạo thành một đối số duy nhất :

$ printf "<%s>\n" "$(dirname $file)"
<.
here>

d. Trích dẫn cả bên trong và bên ngoài, điều này cho câu trả lời chính xác:

$ printf "<%s>\n" "$(dirname "$file")"
<./space here>

(điều đó có thể đơn giản hơn nếu dirnamechỉ xử lý đối số đầu tiên, nhưng điều đó sẽ không cho thấy sự khác biệt giữa các trường hợp a và c.)

Lưu ý rằng với dirname(và có thể cả những người khác) bạn cũng cần thêm --, để ngăn tên tệp được lấy làm tùy chọn trong trường hợp bắt đầu bằng dấu gạch ngang, hãy sử dụng "$(dirname -- "$file")".


16
Lưu ý: Nói chung, trích dẫn không làm tổ trong bash; nhưng trong trường hợp này, việc $( )tạo ra một bối cảnh mới, vì vậy các trích dẫn bên trong nó độc lập với các trích dẫn bên ngoài nó. Và bạn thường muốn trích dẫn trong cả hai bối cảnh.
Gordon Davisson
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.