Hầu hết các câu trả lời đánh vào trường hợp cụ thể mà bạn đang hỏi về. Có một cách tiếp cận chung rằng một người bạn và tôi đã phát triển cho phép tùy tiện trích dẫn trong trường hợp bạn cần phải quote bash lệnh thông qua nhiều lớp mở rộng vỏ, ví dụ, thông qua ssh, su -c
, bash -c
, vv Có một lõi nguyên thủy bạn cần, đây trong bash bản địa:
quote_args() {
local sq="'"
local dq='"'
local space=""
local arg
for arg; do
echo -n "$space'${arg//$sq/$sq$dq$sq$dq$sq}'"
space=" "
done
}
Điều này thực hiện chính xác những gì nó nói: tất nhiên, nó trích dẫn từng đối số riêng lẻ (sau khi mở rộng bash):
$ quote_args foo bar
'foo' 'bar'
$ quote_args arg1 'arg2 arg2a' arg3
'arg1' 'arg2 arg2a' 'arg3'
$ quote_args dq'"'
'dq"'
$ quote_args dq'"' sq"'"
'dq"' 'sq'"'"''
$ quote_args "*"
'*'
$ quote_args /b*
'/bin' '/boot'
Nó thực hiện điều hiển nhiên cho một lớp mở rộng:
$ bash -c "$(quote_args echo a'"'b"'"c arg2)"
a"b'c arg2
(Lưu ý rằng các trích dẫn kép xung quanh $(quote_args ...)
là cần thiết để biến kết quả thành một đối số duy nhất bash -c
.) Và nó có thể được sử dụng chung hơn để trích dẫn chính xác thông qua nhiều lớp mở rộng:
$ bash -c "$(quote_args bash -c "$(quote_args echo a'"'b"'"c arg2)")"
a"b'c arg2
Ví dụ trên:
- shell-trích dẫn từng đối số vào bên trong
quote_args
riêng lẻ và sau đó kết hợp đầu ra kết quả thành một đối số duy nhất với dấu ngoặc kép bên trong.
- vỏ có dấu ngoặc kép
bash
, -c
và kết quả đã một lần được trích dẫn từ bước 1, và sau đó kết hợp kết quả vào một đối số duy nhất với các dấu ngoặc kép bên ngoài.
- gửi mớ hỗn độn đó như đối số ra bên ngoài
bash -c
.
Đó là ý tưởng ngắn gọn. Bạn có thể làm một số thứ khá phức tạp với điều này, nhưng bạn phải cẩn thận về thứ tự đánh giá và về các chuỗi con được trích dẫn. Chẳng hạn, những điều sau đây làm những điều sai (đối với một số định nghĩa của "sai"):
$ (cd /tmp; bash -c "$(quote_args cd /; pwd 1>&2)")
/tmp
$ (cd /tmp; bash -c "$(quote_args cd /; [ -e *sbin ] && echo success 1>&2 || echo failure 1>&2)")
failure
Trong ví dụ đầu tiên, bash ngay lập tức mở rộng quote_args cd /; pwd 1>&2
thành hai lệnh riêng biệt quote_args cd /
và pwd 1>&2
do đó, CWD vẫn còn /tmp
khi pwd
lệnh được thực thi. Ví dụ thứ hai minh họa một vấn đề tương tự đối với Globing. Thật vậy, cùng một vấn đề cơ bản xảy ra với tất cả các mở rộng bash. Vấn đề ở đây là sự thay thế lệnh không phải là một lệnh gọi hàm: nó thực sự đánh giá một tập lệnh bash và sử dụng đầu ra của nó như là một phần của tập lệnh bash khác.
Nếu bạn cố gắng đơn giản thoát khỏi các toán tử shell, bạn sẽ thất bại vì chuỗi kết quả được chuyển đến bash -c
chỉ là một chuỗi các chuỗi được trích dẫn riêng lẻ mà sau đó không được hiểu là các toán tử, điều này rất dễ thấy nếu bạn lặp lại chuỗi đó sẽ đã được thông qua để bash:
$ (cd /tmp; echo "$(quote_args cd /\; pwd 1\>\&2)")
'cd' '/;' 'pwd' '1>&2'
$ (cd /tmp; echo "$(quote_args cd /\; \[ -e \*sbin \] \&\& echo success 1\>\&2 \|\| echo failure 1\>\&2)")
'cd' '/;' '[' '-e' '*sbin' ']' '&&' 'echo' 'success' '1>&2' '||' 'echo' 'failure' '1>&2'
Vấn đề ở đây là bạn đang trích dẫn quá mức. Những gì bạn cần là cho các toán tử không được trích dẫn làm đầu vào cho phần kèm theo bash -c
, có nghĩa là chúng cần nằm ngoài $(quote_args ...)
lệnh thay thế.
Do đó, những gì bạn cần làm theo nghĩa chung nhất là trích dẫn shell từng từ của lệnh không có ý định mở rộng tại thời điểm thay thế lệnh riêng biệt và không áp dụng bất kỳ trích dẫn bổ sung nào cho các toán tử shell:
$ (cd /tmp; echo "$(quote_args cd /); $(quote_args pwd) 1>&2")
'cd' '/'; 'pwd' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")
/
$ (cd /tmp; echo "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
'cd' '/'; [ -e *'sbin' ] && 'echo' 'success' 1>&2 || 'echo' 'failure' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
success
Khi bạn đã thực hiện điều này, toàn bộ chuỗi là trò chơi công bằng để trích dẫn thêm cho các mức đánh giá tùy ý:
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")"
/
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")"
/
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")")"
/
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")"
success
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *sbin ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")"
success
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")")"
success
Vân vân.
Những ví dụ này có vẻ quá căng thẳng khi các từ như success
, sbin
và pwd
không cần được trích dẫn bằng vỏ, nhưng điểm quan trọng cần nhớ khi viết một tập lệnh lấy đầu vào tùy ý là bạn muốn trích dẫn mọi thứ mà bạn không chắc chắn lắm ' Không cần trích dẫn, bởi vì bạn không bao giờ biết khi nào người dùng sẽ ném vào Robert'; rm -rf /
.
Để hiểu rõ hơn những gì đang diễn ra dưới vỏ bọc, bạn có thể chơi xung quanh với hai chức năng trợ giúp nhỏ:
debug_args() {
for (( I=1; $I <= $#; I++ )); do
echo -n "$I:<${!I}> " 1>&2
done
echo 1>&2
}
debug_args_and_run() {
debug_args "$@"
"$@"
}
sẽ liệt kê từng đối số cho một lệnh trước khi thực hiện nó:
$ debug_args_and_run echo a'"'b"'"c arg2
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)"
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'bash'"'"' '"'"'-c'"'"' '"'"''"'"'"'"'"'"'"'"'debug_args_and_run'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'echo'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'a"b'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'c'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'arg2'"'"'"'"'"'"'"'"''"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2