Một kịch bản shell có thể in đối số của nó, được trích dẫn như bạn sẽ viết chúng trên dấu nhắc shell không?


9

Trong một kịch bản shell, sự hiểu biết của tôi là "$@"mở rộng ra các đối số kịch bản, trích dẫn chúng khi cần thiết. Ví dụ, điều này chuyển tiếp các đối số kịch bản tới gcc:

gcc -fPIC "$@"

Khi sử dụng cú pháp bash pass-to-stdin <<<, "@$"nó không hoạt động như tôi mong đợi.

#!/bin/bash
cat <<< "$@"

Gọi kịch bản là ./test.sh foo "bar baz"cho

foo bar baz

Tôi mong chờ

foo "bar baz"

Có cách nào để viết một tập lệnh shell in ra các đối số như bạn sẽ viết chúng trên dấu nhắc shell không? Ví dụ: một gợi ý về việc sử dụng lệnh nào tiếp theo, bao gồm các đối số tập lệnh trong gợi ý.

Câu trả lời:


5

Vâng, "$@"mở rộng đến danh sách các tham số vị trí, một đối số cho mỗi tham số vị trí.

Khi bạn làm:

set '' 'foo bar' $'blah\nblah'
cmd "$@"

cmdđang được gọi với 3 đối số đó: chuỗi rỗng foo barblah<newline>blah. Shell sẽ gọi cuộc gọi execve()hệ thống với một cái gì đó như:

execve("/path/to/cmd", ["cmd", "", "foo bar", "blah\nblah"], [envvars...]);

Nếu bạn muốn xây dựng lại một dòng lệnh shell (đó là mã trong ngôn ngữ shell) sẽ tái tạo cùng một lời gọi đó, bạn có thể làm một cái gì đó như:

awk -v q="'" '
  function shellquote(s) {
    gsub(q, q "\\" q q, s)
    return q s q
  }
  BEGIN {
    for (i = 1; i < ARGC; i++) {
      printf "%s", sep shellquote(ARGV[i])
      sep = " "
    }
    printf "\n"
  }' cmd "$@"

Hoặc với zsh, yêu cầu các loại báo giá khác nhau:

set '' 'foo bar' $'blah\nblah'
$ print -r -- cmd "${(q)@}"
cmd '' foo\ bar blah$'\n'blah
$ print -r -- cmd "${(qq)@}"
cmd '' 'foo bar' 'blah
blah'
$ print -r -- cmd "${(qqq)@}"
cmd "" "foo bar" "blah
blah"
$ print -r -- cmd "${(qqqq)@}"
cmd $'' $'foo bar' $'blah\nblah'

Hoặc với zsh, bashhoặc ksh93(ở đây bash, YMMV với các shell khác):

$ set '' 'foo bar' $'blah\nblah'
$ printf cmd; printf ' %q' "$@"; printf '\n'
cmd '' foo\ bar $'blah\nblah'

Bạn cũng có thể sử dụng tùy chọn xtrace của shell khiến shell in ra những gì nó sẽ thực thi:

(PS4=; set -x; : cmd "$@")
: cmd '' 'foo bar' 'blah
blah'

Ở trên, chúng tôi đã chạy lệnh :no-op với cmdvà các tham số vị trí làm đối số. Vỏ của tôi đã in chúng theo kiểu trích dẫn đẹp phù hợp để tái tạo vỏ. Không phải tất cả các vỏ làm điều đó.


5
`"$@"` expands to the script arguments, quoting them as needed

Không, đây không phải là những gì xảy ra. Gọi một chương trình có một danh sách các đối số, mỗi đối số là một chuỗi. Khi bạn chạy chương trình shell ./test.sh foo "bar baz", này xây dựng một cuộc gọi với ba đối số: ./test.sh, foo, và bar baz. (Đối số zeroth là tên chương trình; điều này cho phép các chương trình biết dưới tên chúng được gọi.) Trích dẫn là một tính năng của trình bao, không phải là một tính năng của các cuộc gọi chương trình. Shell xây dựng danh sách này khi nó thực hiện cuộc gọi.

"$@"trực tiếp sao chép danh sách các đối số được truyền cho tập lệnh hoặc hàm vào danh sách các đối số trong lệnh gọi được sử dụng. Không có trích dẫn liên quan vì không có phân tích cú pháp shell được thực hiện trên các danh sách đó.

Trong cat <<< "$@", bạn đang sử dụng "$@"trong ngữ cảnh yêu cầu một chuỗi. Các <<<operator` đòi hỏi một chuỗi, không phải là một danh sách các chuỗi ký tự. Trong bối cảnh này, bash lấy các thành phần của danh sách và nối chúng với một khoảng trắng ở giữa.

Để gỡ lỗi tập lệnh, nếu bạn chạy set -x( set +xđể tắt), điều đó sẽ kích hoạt chế độ theo dõi trong đó mỗi lệnh được in trước khi được thực thi. Trong bash, dấu vết đó có dấu ngoặc kép cho phép dán lại lệnh vào trình bao (điều này không đúng với mọi shtriển khai).

Nếu bạn có một chuỗi và bạn muốn biến nó thành cú pháp nguồn shell phân tích lại thành chuỗi gốc, bạn có thể bao quanh nó bằng các dấu ngoặc đơn và thay thế mỗi trích dẫn bên trong chuỗi bằng '\''.

for x do
  printf %s "'${x//\'/\'\\\'\'}' "
done
echo

Cú pháp thay thế chuỗi là ksh93 / bash / zsh / mksh-cụ thể. Trong sh đơn giản, bạn cần lặp qua chuỗi.

for raw do
  quoted=
  while case "$raw" in *\'*) true;; *) false;; esac; do
    quoted="$quoted'\\''${raw%%\'*}"
    raw="${raw#*\'}"
  done
  printf %s "'$quoted$raw' "
done
echo

2

"$@" mở rộng các đối số kịch bản, trích dẫn chúng khi cần thiết

Vâng, loại. Đối với các mục đích thực tế cần đủ gần và tài liệu tham khảo có nói rằng"$@" is equivalent to "$1" "$2" ...

Vì vậy, với hai tham số foobar baz, chúng sẽ giống nhau:

echo "$@"
echo "$1" "$2"
echo "foo" "bar baz"

(Ngoại trừ việc nếu các tham số chứa các ký tự đặc biệt thay vì chỉ là các chuỗi đơn giản, chúng sẽ không được mở rộng lại sau khi mở rộng $@$1...)

Nhưng ngay cả khi chúng tôi xem xét $@thay thế bởi các tham số trong dấu ngoặc kép, các trích dẫn sẽ không có ở đó echođể xem, tương tự như điều đó gcccũng không nhận được dấu ngoặc kép.

<<<là một ngoại lệ của quy tắc "$@"== "$1" "$2" ..., nó đã đề cập rõ ràng rằng The result is supplied as a single string to the command on its standard inputsau khi trải qua việc mở rộng tham số và biến đổi và loại bỏ trích dẫn giữa những người khác. Vì vậy, như thường lệ, <<< "foo"chỉ đưa ra foolàm đầu vào, theo cách tương tự somecmd "foo"chỉ đưa ra foonhư một đối số.

Gọi kịch bản là ./test.sh foo "bar baz"[...] Tôi mong đợi foo "bar baz"

Nếu các trích dẫn vẫn còn, nó sẽ vẫn phải được "foo" "bar baz". Shell hoặc bất kỳ lệnh đang chạy nào không có ý tưởng gì khi trích dẫn khi lệnh được chạy. Hoặc nếu thậm chí có bất kỳ trích dẫn nào để nói, cuộc gọi hệ thống chỉ nhận được một danh sách các chuỗi kết thúc null và dấu ngoặc kép chỉ là một tính năng của ngôn ngữ shell. Các ngôn ngữ khác có thể có các quy ước khác.


0

Một giải pháp thay thế cho bash

q='"'; t=( "${@/#/$q}" ); u=( "${t[@]/%/$q}" ); echo ${u[@]}

Bash không hỗ trợ thay thế lồng nhau, vì vậy, xin cảm ơn /programming/12303974/assign-array-to-variable#12304017 để chỉ ra cách gán lại một mảng. Xem man bash( https://linux.die.net/man/1/bash ) để biết chi tiết về mảng, mở rộng và thay thế mẫu (theo mở rộng tham số).

Phân tích

Bash đặt các tham số dòng lệnh như là một mảng trong $@

q giữ ký tự trích dẫn.

Dấu ngoặc kép xung quanh việc mở rộng tham số ${ ... }bảo tồn các tham số riêng lẻ dưới dạng các phần tử riêng biệt và gói chúng vào ( )cho phép bạn gán chúng dưới dạng một mảng cho một biến.

/#/$qtrong một mở rộng tham số thay thế phần đầu của mẫu (như regex ^) bằng char trích dẫn.

/%/$qtrong một mở rộng tham số thay thế phần cuối của mẫu (như regex $) bằng char trích dẫn.

Ca sử dụng: truy vấn MySQL để biết danh sách các địa chỉ email từ dòng lệnh

Có một vài thay đổi đối với các câu lệnh trên để sử dụng một trích dẫn char khác nhau, thêm dấu phẩy giữa các tham số và loại bỏ dấu phẩy cuối cùng. Và dĩ nhiên, tôi rất tệ khi đặt mật khẩu vào lệnh gọi mysql. Nên kiện tôi.

q="'"; t=( "${@/#/$q}" ); u="${t[@]/%/$q,}"; v="u.email in( ${u%,} )"
mysql -uprod_program -h10.90.2.11 -pxxxxxxxxxxxx my_database <<END
select ...
from users u
join ....
where $v # <<<<<<<<<<<<<<<<<< here is where all the hard work pays off :-)
group by user_id, prog_id
;
END

Chỉ cần lưu ý rằng trích dẫn bằng dấu ngoặc kép không hữu ích lắm trong việc bảo vệ dấu gạch chéo ngược, $mở rộng và trích dẫn kép khác. Đó là lý do tại sao các câu trả lời khác sử dụng các trích dẫn đơn trong khi đi đến một số độ dài để xử lý các trích dẫn đơn bên trong chuỗi hoặc sử dụng các tính năng riêng của shell để tạo ra một bản sao được trích dẫn của chuỗi.
ilkkachu

@ilkkachu Ghi chú đúng! Đó là lý do tại sao (trong số những lý do khác) mà tôi nêu lên tất cả các câu trả lời trước đó. Ngoài ra tại sao tôi thêm trường hợp sử dụng này. Hy vọng sự hoàn hảo không phải là kẻ thù của những điều tốt đẹp.
Jeff
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.