Làm thế nào tôi có thể trích dẫn một mở rộng biến trong một chuỗi để tránh chia tách từ?


9
$ myvar="/path to/my directory"
$ sudo bash -c "cd $myvar"

Trong trường hợp như vậy, làm thế nào tôi có thể trích dẫn $myvarđể tránh chia tách từ vì khoảng trắng trong giá trị của myvar?


4
Cho dù bạn giải quyết vấn đề này như thế nào, nó vẫn sẽ không làm được gì nhiều. Các cdchỉ có tác dụng bên trong bash -cvỏ.
Kusalananda

Tôi chỉ đưa ra một ví dụ tối thiểu cho câu hỏi duy nhất, đó không phải là một giải pháp hiệu quả cho vấn đề thực sự của tôi, đó là unix.stackexchange.com/a/269080/674 cộng với trong chuỗi lệnh được cung cấp cho sudo bash -ctôi có mở rộng biến đổi thành một tên đường dẫn có thể chứa khoảng trắng.
Tim

Câu trả lời:


13

Không có phân chia từ (như trong tính năng phân tách các biến khi mở rộng không được trích dẫn) trong mã đó như $myvarkhông được trích dẫn.

Tuy nhiên, có một lỗ hổng tiêm lệnh như $myvarđược mở rộng trước khi được chuyển đến bash. Vì vậy, nội dung của nó được hiểu là mã bash!

Các không gian trong đó sẽ khiến một số đối số được truyền vào cd, không phải vì phân tách từ mà vì chúng sẽ được phân tích cú pháp dưới dạng một số mã thông báo trong cú pháp shell. Với giá trị bye;reboot, nó sẽ khởi động lại!

Ở đây, bạn muốn:

sudo bash -c 'cd -P -- "$1"' bash "$myvar"

(trong đó bạn chuyển nội dung $myvarlà đối số đầu tiên của tập lệnh nội tuyến đó; lưu ý cách cả hai $myvar$1được trích dẫn cho trình bao tương ứng của chúng để ngăn chặn phân tách từ IFS (và toàn cầu hóa)).

Hoặc là:

sudo MYVAR="$myvar" bash -c 'cd -P -- "$MYVAR"'

(nơi bạn chuyển nội dung $myvartrong một biến môi trường).

Tất nhiên, bạn sẽ không đạt được bất cứ điều gì hữu ích bằng cách chỉ chạy cdtrong tập lệnh nội tuyến đó (ngoài việc kiểm tra xem rootcó thể cdvào đó không). Có lẽ, bạn muốn kịch bản cdđó ở đó và sau đó làm một cái gì đó khác như:

sudo bash -c 'cd -P -- "$1" && do-something' bash "$myvar"

Nếu mục đích là sử dụng sudođể có thể cdvào một thư mục mà bạn không có quyền truy cập, thì điều đó không thể thực sự hoạt động.

sudo sh -c 'cd -P -- "$1" && exec bash' sh "$myvar"

sẽ bắt đầu tương tác bashvới thư mục hiện tại của nó trong $myvar. Nhưng cái vỏ đó sẽ chạy như root.

Bạn có thể làm:

sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"

Để có được sự tương tác không có đặc quyền bashvới thư mục hiện tại $myvar, nhưng nếu bạn không có quyền cdvào thư mục đó ngay từ đầu, bạn sẽ không thể làm bất cứ điều gì trong thư mục đó ngay cả khi đó là thư mục làm việc hiện tại của bạn.

$ myvar=/var/spool/cron/crontabs
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ ls
ls: cannot open directory '.': Permission denied

Một ngoại lệ sẽ là nếu bạn có quyền tìm kiếm đối với chính thư mục đó nhưng không phải là một trong các thành phần thư mục của đường dẫn của nó:

$ myvar=1/2
$ mkdir -p "$myvar"
$ chmod 0 1
$ cd 1/2
cd: permission denied: 1/2
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ pwd
/home/stephane/1/2
bash-4.4$ mkdir 3
bash-4.4$ ls
3
bash-4.4$ cd "$PWD"
bash: cd: /home/stephane/1/2: Permission denied

Nói đúng ra, đối với các giá trị $myvarlike $(seq 10)(theo nghĩa đen), tất nhiên sẽ có sự phân tách từ khi mở rộng thay thế lệnh đó bằng bash shell bắt đầu nhưroot


4

Có một phép thuật trích dẫn mới (bash 4.4) cho phép bạn trực tiếp làm những gì bạn muốn. Tùy thuộc vào bối cảnh lớn hơn, sử dụng một trong các kỹ thuật khác có thể tốt hơn, nhưng nó hoạt động trong bối cảnh hạn chế này.

sudo bash -c "cd ${myvar@Q}; pwd"

4

Nếu bạn không thể tin tưởng vào nội dung của $myvar, cách tiếp cận hợp lý duy nhất là sử dụng GNU printfđể tạo phiên bản thoát của nó:

#!/bin/bash

myvar=$(printf '%q' "$myvar")
bash -c "echo $myvar"

Như printftrang người đàn ông nói:

%q

ARGUMENT được in ở định dạng có thể được sử dụng lại làm đầu vào shell, thoát các ký tự không in được bằng $''cú pháp POSIX được đề xuất .


Xem câu trả lời của tôi cho một phiên bản di động của điều này.
R .. GitHub DỪNG GIÚP ICE

GNU printfsẽ chỉ được gọi ở đó trên các hệ thống GNU và nếu nó $(printf '%q' "$myvar")được chạy trong một vỏ printfkhông được dựng sẵn. Hầu hết các vỏ đã được xây printfdựng ngày nay. Không phải tất cả đều hỗ trợ %qmặc dù và khi họ làm, họ sẽ trích dẫn điều theo định dạng được hỗ trợ bởi trình bao tương ứng, có thể không nhất thiết phải giống như cách hiểu bash. Trên thực tế, bashcủa printfthất bại trong việc trích dẫn những điều đúng cho bản thân thậm chí đối với một số giá trị của $myvarở một số vùng.
Stéphane Chazelas

Với bash-4.3ít nhất, đó vẫn là một lỗ hổng tiêm lệnh với myvar=$'\xa3``reboot`\xa3`' ví dụ trong miền địa phương zh_HK.big5hkscs.
Stéphane Chazelas

3

Trước hết, như những người khác đã lưu ý rằng cdlệnh của bạn là vô ích vì nó xảy ra trong bối cảnh lớp vỏ ngay lập tức thoát ra. Nhưng nói chung câu hỏi có ý nghĩa. Những gì bạn cần là một cách để trích dẫn một chuỗi tùy ý , để nó có thể được sử dụng trong ngữ cảnh nơi nó sẽ được hiểu là một chuỗi trích dẫn shell. Tôi có một ví dụ về điều này trong trang thủ thuật sh của tôi :

quote () { printf %s\\n "$1" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/" ; }

Với chức năng này, bạn có thể thực hiện:

myvar="/path to/my directory"
sudo bash -c "cd $(quote "$myvar")"

3

Những gì Stéphane Chazelas gợi ý chắc chắn là cách tốt nhất để làm điều này. Tôi sẽ chỉ cung cấp một câu trả lời về cách trích dẫn một cách an toàn với cú pháp mở rộng tham số thay vì sử dụng một quy sedtrình con, như trong câu trả lời của R ..

myvar='foo \'\''"bar'

quote() {
  printf "'%s'" "${1//\'/\'\\\'\'}"
}

printf "value: %s\n" "$myvar"
printf "quoted: %s\n" "$(quote "$myvar")"
bash -c "printf 'interpolated in subshell script: %s\n' $(quote "$myvar")"

Đầu ra của tập lệnh đó là:

value: foo \'"bar
quoted: 'foo \'\''"bar'
interpolated in subshell script: foo \'"bar

1

Vâng, có từ tách.
Vâng, về mặt kỹ thuật, dòng lệnh tách trên bash phân tích cú pháp dòng.

Trước tiên chúng ta hãy trích dẫn đúng:

Các giải pháp đơn giản nhất (và không an toàn) để có được lệnh hoạt động như tôi tin rằng bạn sẽ muốn nó là:

bash -c "cd \"$myvar\""

Hãy xác nhận những gì xảy ra (giả sử myvar="/path to/my directory"):

$ bash -c "printf '<%s>\n' $myvar"
<./path>
<to/my>
<directory>

Như bạn có thể thấy, chuỗi đường dẫn đã được phân chia trên các khoảng trắng. Và:

$ bash -c "printf '<%s>\n' \"$myvar\""
<./path to/my directory>

Không bị chia rẽ.

Tuy nhiên, lệnh (như được viết) là không an toàn. Sử dụng tốt hơn:

$ bash -c "cd -P -- \"$myvar\"; pwd"
/home/user/temp/path to/my directory

Điều đó sẽ bảo vệ cd trước các tệp bắt đầu bằng dấu gạch ngang (-) hoặc theo các liên kết có thể dẫn đến sự cố.

Điều đó sẽ hoạt động nếu bạn có thể tin rằng chuỗi trong $myvarlà một đường dẫn.
Tuy nhiên, "tiêm mã" vẫn có thể vì bạn đang "thực thi một chuỗi":

$ myvar='./path to/my directory";date;"true'
$ bash -c "printf '<%s> ' \"$myvar\"; echo"
<./path to/my directory> Tue May  8 12:25:51 UTC 2018

Bạn cần một trích dẫn mạnh mẽ hơn của chuỗi, như:

$ bash -c 'printf "<%s> " "$1"; echo' _ "$myvar"
<./path to/my directory";date -u;"true>

Như bạn có thể thấy ở trên, giá trị không được diễn giải mà chỉ được sử dụng như "$1'.

Bây giờ chúng ta có thể (an toàn) sử dụng sudo:

$ sudo bash -c 'cd -P -- "$1"; pwd' _ "$myvar"
_: line 0: cd: ./path to/my directory";date -u;"true: No such file or directory

Nếu có một vài đối số, bạn có thể sử dụng:

$ sudo bash -c 'printf "<%s>" "$@"' _ "$val1" "$val2" "$val3"

-2

Dấu ngoặc đơn sẽ không hoạt động ở đây, kể từ đó biến của bạn sẽ không được mở rộng. Nếu bạn tự tin rằng biến cho đường dẫn của bạn được khử trùng, giải pháp đơn giản nhất là chỉ cần thêm dấu ngoặc kép xung quanh việc mở rộng kết quả, một khi nó nằm trong vỏ bash mới:

sudo bash -c "cd \"$myvar\""

2
Vẫn là một lỗ hổng tiêm lệnh, như đối với các giá trị $myvarlike $(reboot)hoặc"; reboot; #
Stéphane Chazelas

1
việc sử dụng sudo bash -c "cd -P -- '$myvar'"sẽ giới hạn vấn đề chỉ với các giá trị $myvar chứa các ký tự trích dẫn đơn lẻ sẽ dễ dàng vệ sinh hơn.
Stéphane Chazelas
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.