Làm cách nào tôi có thể tạo đối số cho lệnh khác thông qua thay thế lệnh


11

Theo dõi từ: hành vi bất ngờ trong thay thế lệnh shell

Tôi có một lệnh có thể lấy một danh sách lớn các đối số, một số trong đó có thể chứa các khoảng trắng một cách hợp pháp (và có thể là những thứ khác)

Tôi đã viết một tập lệnh có thể tạo ra các đối số đó cho tôi, bằng dấu ngoặc kép, nhưng tôi phải sao chép và dán kết quả đầu ra, vd

./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>

Tôi đã cố gắng hợp lý hóa điều này bằng cách đơn giản là làm

./othercommand $(./somecommand)

và chạy vào hành vi bất ngờ được đề cập trong câu hỏi trên. Câu hỏi là - có thể sử dụng thay thế lệnh một cách đáng tin cậy để tạo ra các đối số để othercommandđưa ra rằng một số đối số yêu cầu trích dẫn và điều này không thể thay đổi?


Phụ thuộc vào những gì bạn có nghĩa là "đáng tin cậy". Nếu bạn muốn lệnh tiếp theo lấy đầu ra chính xác như nó xuất hiện trên màn hình và áp dụng quy tắc shell cho nó, thì có evalthể có thể được sử dụng, nhưng nó thường không được khuyến khích. xargscũng là một cái gì đó để xem xét
Sergiy Kolodyazhnyy

Tôi muốn (mong đợi) đầu ra từ somecommandtrải qua phân tích cú pháp shell thông thường
user1207217

Như tôi đã nói trong câu trả lời của mình, sử dụng một số ký tự khác để tách trường (như :) ... giả sử rằng ký tự đó sẽ không nằm trong đầu ra.
Olorin

Nhưng điều đó không thực sự đúng bởi vì nó không tuân theo các quy tắc trích dẫn, đó là những gì nó nói về
user1207217

2
Bạn có thể gửi một ví dụ thực tế? Ý tôi là, đầu ra thực tế của lệnh đầu tiên và cách bạn muốn nó tương tác với lệnh thứ hai.
nxnev

Câu trả lời:


10

Tôi đã viết một kịch bản có thể tạo ra các đối số đó cho tôi, với các trích dẫn

Nếu đầu ra được trích dẫn chính xác cho shell và bạn tin tưởng đầu ra , thì bạn có thể chạy evaltrên nó.

Giả sử bạn có một shell hỗ trợ các mảng, tốt nhất nên sử dụng một shell để lưu trữ các đối số bạn nhận được.

Nếu ./gen_args.shtạo đầu ra như thế 'foo bar' '*' asdf, thì chúng ta có thể chạy eval "args=( $(./gen_args.sh) )"để điền vào một mảng được gọi argsvới kết quả. Đó sẽ là ba yếu tố foo bar, *, asdf.

Chúng ta có thể sử dụng "${args[@]}"như bình thường để mở rộng các thành phần mảng riêng lẻ:

$ eval "args=( $(./gen_args.sh) )"
$ for var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

(Lưu ý các trích dẫn. "${array[@]}"Mở rộng cho tất cả các thành phần dưới dạng các đối số riêng biệt không được sửa đổi. Không có dấu ngoặc kép, các thành phần mảng có thể bị tách từ. Xem ví dụ trang Mảng trên BashGuide .)

Tuy nhiên , evalsẽ vui vẻ chạy bất kỳ thay thế shell nào, vì vậy $HOMEtrong đầu ra sẽ mở rộng sang thư mục chính của bạn và một sự thay thế lệnh sẽ thực sự chạy một lệnh trong shell đang chạy eval. Một đầu ra của "$(date >&2)"sẽ tạo ra một phần tử mảng trống duy nhất và in ngày hiện tại trên thiết bị xuất chuẩn. Đây là một mối quan tâm nếu gen_args.shlấy dữ liệu từ một số nguồn không đáng tin cậy, như một máy chủ khác qua mạng, tên tệp được tạo bởi người dùng khác. Đầu ra có thể bao gồm các lệnh tùy ý. (Nếu get_args.shbản thân nó là độc hại, nó sẽ không cần xuất bất cứ thứ gì, nó chỉ có thể chạy các lệnh độc hại trực tiếp.)


Một cách khác để trích dẫn shell, khó phân tích mà không có eval, sẽ là sử dụng một số ký tự khác làm dấu phân cách trong đầu ra của tập lệnh của bạn. Bạn cần chọn một thứ không cần thiết trong các đối số thực tế.

Hãy chọn #và có đầu ra kịch bản foo bar#*#asdf. Bây giờ chúng ta có thể sử dụng mở rộng lệnh không trích dẫn để phân chia đầu ra của lệnh cho các đối số.

$ IFS='#'                          # split on '#' signs
$ set -f                           # disable globbing
$ args=( $( ./gen_args3.sh ) )     # assign the values to the arrayfor var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

Bạn sẽ cần thiết lập IFSlại sau nếu bạn phụ thuộc vào phân tách từ ở nơi khác trong tập lệnh ( unset IFSnên hoạt động để biến nó thành mặc định) và cũng có thể sử dụng set +fnếu bạn muốn sử dụng Globing sau này.

Nếu bạn không sử dụng Bash hoặc một số shell khác có mảng, bạn có thể sử dụng các tham số vị trí cho điều đó. Thay thế args=( $(...) )bằng set -- $(./gen_args.sh)và sử dụng "$@"thay vì "${args[@]}"sau đó. (Ở đây cũng vậy, bạn cần trích dẫn xung quanh "$@", nếu không các tham số vị trí có thể bị chia tách từ.)


Tốt nhất của cả hai thế giới!
Olorin

Bạn có thêm một nhận xét cho thấy tầm quan trọng của việc trích dẫn ${args[@]}- điều này không hiệu quả với tôi
1207217

@ user1207217, vâng, bạn đúng. Đó là điều tương tự với mảng và "${array[@]}"như với "$@". Cả hai cần phải được trích dẫn, hoặc nếu không thì việc chia từ sẽ phá vỡ các thành phần mảng thành các phần.
ilkkachu

6

Vấn đề là một khi somecommandtập lệnh của bạn đưa ra các tùy chọn cho othercommand, các tùy chọn thực sự chỉ là văn bản và không phải là sự phân tích chuẩn của shell (bị ảnh hưởng bởi bất kỳ điều gì $IFSxảy ra và các tùy chọn shell nào có hiệu lực, trong trường hợp chung bạn sẽ không được kiểm soát).

Thay vì sử dụng somecommandđể xuất các tùy chọn, việc sử dụng nó để gọi sẽ dễ dàng hơn, an toàn hơn và mạnh mẽ hơn othercommand. Tập somecommandlệnh sau đó sẽ là tập lệnh bao bọc xung quanh othercommandthay vì một tập lệnh trợ giúp nào đó mà bạn phải nhớ để gọi theo một cách đặc biệt nào đó như là một phần của dòng lệnh otherscript. Các kịch bản trình bao bọc là một cách rất phổ biến để cung cấp một công cụ chỉ gọi một số công cụ tương tự khác với một bộ tùy chọn khác (chỉ cần kiểm tra xem filecác lệnh trong /usr/binthực sự là các trình bao bọc tập lệnh shell).

Trong bash, kshhoặc zsh, bạn có thể dễ dàng một tập lệnh bao bọc sử dụng một mảng để giữ các tùy chọn riêng lẻ othercommandnhư vậy:

options=( "hi there" "nice weather" "here's a star" "*" )
options+=( "bonus bumblebee!" )  # add additional option

Sau đó gọi othercommand(vẫn trong tập lệnh bao bọc):

othercommand "${options[@]}"

Việc mở rộng "${options[@]}"sẽ đảm bảo rằng mỗi phần tử của optionsmảng được trích dẫn riêng và được trình bày thành othercommandcác đối số riêng biệt.

Người sử dụng trình bao bọc sẽ không biết thực tế rằng nó thực sự đang gọi othercommand, điều gì đó sẽ không đúng nếu tập lệnh thay vào đó chỉ tạo các tùy chọn dòng lệnh cho othercommandđầu ra.

Trong /bin/sh, sử dụng $@để giữ các tùy chọn:

set -- "hi there" "nice weather" "here's a star" "*"
set -- "$@" "bonus bumblebee!"  # add additional option

othercommand "$@"

( setLà lệnh dùng để thiết lập các tham số vị trí $1, $2, $3vv Đây là những gì tạo nên các mảng $@trong một POSIX vỏ tiêu chuẩn. Các ban đầu --là để báo hiệu cho setrằng không có tùy chọn nào, chỉ tranh luận. Các --được thực sự chỉ cần thiết nếu giá trị đầu tiên xảy ra là một cái gì đó bắt đầu với -).

Lưu ý rằng đó là dấu ngoặc kép xung quanh $@${options[@]}đảm bảo rằng các phần tử không bị tách từ riêng lẻ (và tên tệp được đặt trên toàn cầu).


bạn có thể giải thích set --?
dùng1207217

@ user1207217 Đã thêm giải thích để trả lời.
Kusalananda

4

Nếu somecommandđầu ra là cú pháp shell tốt đáng tin cậy, bạn có thể sử dụng eval:

$ eval sh test.sh $(echo '"hello " "hi and bye"')
hello 
hi and bye

Nhưng bạn phải chắc chắn rằng đầu ra có trích dẫn hợp lệ và nếu không, bạn cũng có thể sẽ chạy các lệnh bên ngoài tập lệnh:

$ cat test.sh 
for var in "$@"
do
    echo "|$var|"
done
$ ls
bar  baz  test.sh
$ eval sh test.sh $(echo '"hello " "hi and bye"; echo rm *')
|hello |
|hi and bye|
rm bar baz test.sh

Lưu ý rằng echo rm bar baz test.shđã không được chuyển đến tập lệnh (vì ;) và được chạy dưới dạng một lệnh riêng biệt. Tôi đã thêm |xung quanh $varđể làm nổi bật điều này.


Nói chung, trừ khi bạn hoàn toàn có thể tin tưởng vào đầu ra của somecommand, không thể tin cậy sử dụng đầu ra của nó để xây dựng chuỗi lệnh.

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.