Làm thế nào để truyền các đối số bash script cho một subshell


13

Tôi có một tập lệnh bao bọc thực hiện một số công việc và sau đó chuyển các tham số ban đầu sang một công cụ khác:

#!/bin/bash
# ...
other_tool -a -b "$@"

Điều này hoạt động tốt, trừ khi "công cụ khác" được chạy trong một lớp con:

#!/bin/bash
# ...
bash -c "other_tool -a -b $@"

Nếu tôi gọi tập lệnh bao bọc của tôi như thế này:

wrapper.sh -x "blah blup"

sau đó, chỉ đối số ban đầu đầu tiên (-x) được trao cho "other_tool". Trong thực tế, tôi không tạo một lớp con, nhưng chuyển các đối số ban đầu sang một vỏ trên điện thoại Android, điều này không tạo ra bất kỳ sự khác biệt nào:

#!/bin/bash
# ...
adb sh -c "other_tool -a -b $@"

Câu trả lời:


14

printfLệnh của Bash có một tính năng sẽ trích dẫn / thoát / bất cứ chuỗi nào, miễn là cả cha và phụ đều thực sự là bash, điều này sẽ hoạt động:

[Chỉnh sửa: như siegi đã chỉ ra trong một bình luận, nếu bạn làm điều này theo cách rõ ràng sẽ có vấn đề khi không có đối số nào được cung cấp, trong đó nó hoạt động như thực sự có một đối số trống. Tôi đã thêm một cách giải quyết bên dưới, bao bọc chuỗi định dạng ${1+}, chỉ bao gồm chuỗi định dạng nếu đối số đầu tiên được xác định . Đó là một chút của một kluge, nhưng nó hoạt động.]

#!/bin/bash

quoted_args="$(printf "${1+ %q}" "$@")" # Note: this will have a leading space before the first arg
# echo "Quoted args:$quoted_args" # Uncomment this to see what it's doing
bash -c "other_tool -a -b$quoted_args"

Lưu ý rằng bạn cũng có thể làm điều đó trong một dòng duy nhất: bash -c "other_tool -a -b$(printf "${1+ %q}" "$@")"


Cảm ơn bạn! Điều này làm việc rất tốt cho tôi.
DribblzAroundU82

Điều này không hoạt động chính xác, khi không có đối số nào được thông qua (nó chuyển một đối số trống duy nhất thay vì không có đối số).
siegi

1
@siegi Bắt tốt; Tôi đã thêm một cách giải quyết cho việc này.
Gordon Davisson

4

Không có giải pháp nào hoạt động tốt. Chỉ cần vượt qua x / \ \ \ "b \" / aaaaa / \ 'xxx \ yyyy \' / zz \ ​​"offf \" làm tham số và chúng thất bại.

Dưới đây là một trình bao bọc đơn giản xử lý mọi trường hợp. Lưu ý cách nó thoát khỏi mỗi đối số hai lần.

#!/usr/bin/env bash
declare -a ARGS
COUNT=$#
for ((INDEX=0; INDEX<COUNT; ++INDEX))
do
    ARG="$(printf "%q" "$1")"
    ARGS[INDEX]="$(printf "%q" "$ARG")"
    shift
done

ls -l ${ARGS[*]}

1
cũng hữu ích nếu bạn đang cố gắng ghép $ @ như một phần của đối số chuỗi được trích dẫn cuối cùng như '/ bin / foo' "$ @" '--opt' và phát hiện ra rằng nó không xuất hiện như bạn mong đợi .
JRG

1
Cảm ơn chúa. Đã đấu tranh với vấn đề này và dường như không có gì làm việc. Điều này đã giải quyết nó. Sẽ tốt hơn nếu bằng cách nào đó, một bash tích hợp để gọi thoát kép khi xem xét vấn đề này xuất hiện rất nhiều.
MooGoo

2

Thay đổi $@thành $*. Tôi đã làm một thử nghiệm địa phương nhỏ và nó hoạt động trong trường hợp của tôi.

#!/bin/sh
bash -c "echo $*"
bash -c "echo $@"

Lưu như test.shvà làm cho nó thực thi được

$ ./test.sh foo bar
foo bar
foo

Có một sự khác biệt tinh tế giữa $*$@, như bạn có thể thấy. Xem ví dụ: http://ss64.com/bash/syntax-parameter.html


Đối với câu hỏi tiếp theo trong các bình luận: bạn cần thoát, ví dụ: "hai lần" khoảng trắng để truyền một chuỗi có dấu phân cách làm đối số kết hợp, ví dụ: test.shđược sửa đổi thành wctrình bao bọc:

#!/bin/sh
bash -c "wc $*"

Những công việc này:

$ touch test\ file
$ ./test.sh -l "test\ file"
0 test file

nhưng:

$ ./test.sh -l "test file"
wc: test: No such file or directory
wc: file: No such file or directory
0 total

Thật không may, điều này cũng không hoạt động. Nếu bạn gọi trình bao bọc như thế này: wrapper.sh -x "blah blup"thì lớp con nhận được BA tham số (-x, blah, blup) thay vì TWO (-x, "blah blup")
Ralf Holly

Bạn cần "thoát hai lần" đối số nếu bạn muốn phân tách không gian được bảo tồn, tức là "Blah\ blup". Hãy thử nó và xem nếu nó hoạt động.
Daniel Andersson

2
Có vẻ như nó hoạt động, tuy nhiên, điều này sẽ yêu cầu người gọi của Wrapper.sh để thêm các lối thoát và tôi đang tìm kiếm một giải pháp minh bạch.
Ralf Holly

2

Không thành công vì bạn ép một mảng (tham số vị trí) thành một chuỗi. "$@"là huyền diệu bởi vì nó cung cấp cho bạn mỗi tham số riêng biệt như một chuỗi trích dẫn đúng. Thêm văn bản bổ sung phá vỡ phép thuật: "blah $@"chỉ là một chuỗi.

Điều này có thể giúp bạn đến gần hơn:

cmd="other_tool -a -b"
for parm in "$@"; do cmd+=" '$parm'"; done
adb sh -c "$cmd"

Tất nhiên, bất kỳ tham số có chứa một trích dẫn sẽ gây rắc rối.


2

Ok, giải thích thêm:

$ cat /tmp/test.sh
#! /bin/bash

echo '$@='"$@"

set -v # "debug"

sh -c 'echo $@' "$@" # gremlins steal the first option

sh -c 'echo $@' -- "$@" # We defend the option with two knifes

$ bash -e /tmp/test.sh first second third
$@=first second third

sh -c 'echo $@' "$@" # gremlins steal the first option
second third

sh -c 'echo $@' -- "$@" # We defend the option with two knifes
first second third

1

Tôi đã thử nhiều giải pháp khác nhau, một nguồn tài nguyên tốt bao gồm thông tin cơ bản và các giải pháp thay thế là ví dụ BashFAQ / 096 trên Wiki của Greg (hay còn gọi là GreyCat) . Nhìn chung, tôi thấy hai cái sau là dễ đọc nhất (từ những cái đang hoạt động):


Vì Bash 4.4 (theo như tôi có thể nói từ TIN TỨC ) nên có thể sử dụng mở rộng tham số @Q như thế này:

adb sh -c "other_tool -a -b ${*@Q}"

Lưu ý rằng tôi sử dụng $* ở đây thay $@vì bởi vì bạn muốn "other_tool -a -b ${*@Q}"trở thành một chuỗi thay vì một chuỗi cho mỗi đối số được truyền.

Nếu bạn muốn làm tương tự với một biến bash mảng bạn sẽ cần cú pháp ${ARRAY[*]@Q}(trong dấu ngoặc kép).


Nếu bạn không có sẵn Bash 4.4 trở lên hoặc không chắc chắn, đây là giải pháp ưa thích của tôi:

function escapeBashArgs() {
    local arg separator=""
    for arg
    do
        printf "%s%q" "$separator" "$arg"
        separator=" "
    done
}

adb sh -c "other_tool -a -b $(escapeBashArgs "$@")"

Lưu ý rằng ở đây bạn cần sử dụng "$@"thay vì $@hoặc "$*"hoặc$* vì bạn không muốn chia từ trong các đối số, do đó, các biến thể không trích dẫn không thể được sử dụng và bạn muốn giữ nguyên số lượng đối số, vì vậy "$*"không thể sử dụng vì nó sẽ tham gia tất cả các đối số cho một chuỗi. Hàm sau đó trả về tất cả các đối số trong một chuỗi.

Nếu bạn không quan tâm đến không gian bổ sung phía trước đối số đầu tiên, bạn có thể thay đổi printfchuỗi định dạng thành " %q"và xóa separatorbiến. Hoặc bạn có thể sử dụng một lớp lót từ câu trả lời của Gordon Davissons .


Giải pháp này hoạt động với tất cả các trường hợp tôi có thể đưa ra, đặc biệt là:

  • Không có đối số: escapeBashArgskhông có gì
  • Đối số trống: escapeBashArgs "" ""'' ''
  • Các đối số chỉ có khoảng trắng: escapeBashArgs " " " "' ' ' 'hoặc \ \ \ \ \( không gian cuối cùng được ăn bởi trình kết xuất trang web này )
  • Các đối số có khoảng cách và dòng mới đặc biệt: escapeBashArgs "a b" c\ d "arg with newline"'a b' 'c d' $'arg with\nnewline'hoặc a\ \ \ \ \ \ b c\ d $'arg with\nnewline'( dòng mới nằm giữa withnewline, trên các vị trí khác, đó là do gói dòng trên trang web này )
  • Đối số với các ký tự đặc biệt: escapeBashArgs '$"'\''({:})''$"'\''({:})'hoặc\$\"\'\(\{:\}\)
  • Ví dụ từ câu trả lời của jcayzacs : escapeBashArgs x/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"'x/ "b"/aaaaa/'\''xxx yyyy'\''/zz"offf"'hoặcx/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"

(Đã thử nghiệm với GNU bash 5.0.3 (1) -release.)


-2
#! /bin/bash
sh -c 'other_tool -a -b "$@"' -- "$@"

3
Chào mừng bạn đến với Siêu người dùng! Chúng tôi đang tìm kiếm câu trả lời dài cung cấp một số giải thích và bối cảnh. Đừng chỉ đưa ra một câu trả lời một dòng; giải thích tại sao câu trả lời của bạn là đúng, lý tưởng với trích dẫn. Câu trả lời không bao gồm giải thích có thể được gỡ bỏ.
G-Man nói 'Phục hồi Monica'
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.