bashスクリプトの引数をサブシェルに渡す方法


13

いくつかの作業を行い、元のパラメーターを別のツールに渡すラッパースクリプトがあります。

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

「他のツール」がサブシェルで実行されない限り、これは正常に機能します。

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

次のようにラッパースクリプトを呼び出すと、

wrapper.sh -x "blah blup"

次に、最初の元の引数(-x)のみが「other_tool」に渡されます。実際には、私はサブシェルを作成しませんが、元の引数をAndroidフォンのシェルに渡しますが、違いはありません。

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

回答:


14

Bashのprintfコマンドには、文字列を引用/エスケープ/あらゆるものにする機能があるため、親とサブシェルの両方が実際にbashである限り、これは機能するはずです。

[編集:siegiがコメントで指摘したように、これを行うと、引数が指定されていないときに問題が発生し、実際には1つの空の引数があったかのように動作するという明らかな問題が発生します。以下に回避策を追加しました。フォーマット文字列をでラップします。これには、最初の引数が定義されている場合に${1+}のみフォーマット文字列が含まます。少しクルージですが、うまくいきます。]

#!/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"

1行で行うこともできます。 bash -c "other_tool -a -b$(printf "${1+ %q}" "$@")"


ありがとうございました!これは私にとって非常にうまくいきました。
DribblzAroundU82 2019年

引数が渡されない場合、これは正しく機能しません(引数なしの代わりに単一の空の引数が渡されます)。
siegi

1
@siegi良いキャッチ。これに対する回避策を追加しました。
Gordon Davisson

4

どのソリューションもうまく機能しません。パラメータとしてx / \ \ \ "b \" / aaaaa / \ 'xxx \ yyyy \' / zz \ "offf \"を渡すだけで失敗します。

以下は、すべてのケースを処理する単純なラッパーです。各引数を2回エスケープする方法に注意してください。

#!/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
$ /を '/ bin / foo' "$ @" '--opt'のように引用符で囲まれた文字列引数の一部として連結しようとしていて、期待どおりに出力されないことがわかった場合にも役立ちます。 。
jrg 2014年

1
ああ神に感謝します。この問題に苦しんでいて、何もうまくいかなかったようです。これで解決しました。この問題が頻繁に発生することを考慮して、ダブルエスケープを呼び出すのがどういうわけかbash組み込みだったとしたら、すばらしいでしょう。
MooGoo 2018

2

に変更$@$*ます。私は小さなローカルテストを行いましたが、私の場合はうまくいきます。

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

保存しtest.shて実行可能にすると、

$ ./test.sh foo bar
foo bar
foo

ご覧のとおり、との間には微妙な違いが$*あり$@ます。たとえば、http//ss64.com/bash/syntax-parameters.htmlを参照してください


コメントのフォローアップ質問の場合:たとえばラッパーにtest.sh変更を加えて、結合された引数として区切り文字を含む文字列を渡すには、たとえば空白「2回」をエスケープする必要がありwcます。

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

これは機能します:

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

だが:

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

残念ながら、これも機能しません。次のようにラッパーを呼び出すと、wrapper.sh -x "blah blup"サブシェルはTWO(-x、 "blah blup")ではなくTHREEパラメーター(-x、blah、blup)を取得します
Ralf Holly

スペースセパレータを保持する場合は、引数を「ダブルエスケープ」する必要があります"Blah\ blup"。試してみて、機能するかどうかを確認してください。
Daniel Andersson

2
機能しているように見えますが、wrapper.shの呼び出し元にエスケープを追加する必要があるため、透過的な解決策を探しています。
ラルフホリー

2

配列(位置パラメーター)を文字列に強制変換しているため、失敗します。"$@"適切に引用された文字列として各個別のパラメーターを提供するため、魔法です。テキストを追加すると魔法"blah $@"が解けます。これは単一の文字列です。

これで近づくかもしれません:

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

もちろん、単一引用符を含むパラメーターは問題を引き起こします。


2

OK、その他の説明:

$ 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

私は多くの異なる解決策を試しました。背景情報や代替案を含む優れたリソースは、たとえばGreg(別名GreyCat)WikiのBashFAQ / 096です。まとめると、次の2つが(動作するものから)最も読みやすいとわかりました。


Bash 4.4以降(私がNEWSからわかる範囲で)、次のようにパラメーター展開 を使用することが可能@Qです。

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

渡された引数ごとに1つの文字列ではなく1つの文字列になりたいので、$*ここでは代わりにここを使用しています。$@"other_tool -a -b ${*@Q}"

bash配列変数を使用して同じことを行う場合は、${ARRAY[*]@Q}(引用符内の)構文が必要です。


あなたがいる場合バッシュ4.4以降が利用できていないかわからない、これは私の好みのソリューションです。

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

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

ここにあなたが使用する必要があることを注意"$@"の代わりに、$@または"$*"あるいは$*引用なしの変異体を使用することができない、とあなたは引数の数が保存されるようにしたいので、あなたは、引数内の単語分割をしたくないので"$*"、それは参加するだろうとして使用することはできません単一の文字列へのすべての引数。その後、関数はすべての引数を単一の文字列で返します。

最初の引数の前の追加スペースを気にしない場合は、printfフォーマット文字列をに変更して" %q"separator変数を削除できます。または、Gordon Davissons answerのワンライナーを使用することもできます。


この解決策は、私が思いつく可能性のあるすべてのケースで機能します。特に:

  • 引数なし:escapeBashArgsなし
  • 空の引数:escapeBashArgs "" ""'' ''
  • スペースのみの引数:escapeBashArgs " " " "' ' ' 'または\ \ \ \ \最後のスペースはこのサイトレンダラーによって使用されます
  • 特別な間隔と改行のある引数:escapeBashArgs "a b" c\ d "arg with newline"'a b' 'c d' $'arg with\nnewline'またはa\ \ \ \ \ \ b c\ d $'arg with\nnewline'改行はとの間にwithあります。newline他の位置では、このサイトでの行の折り返しが原因です
  • 特殊文字を含む引数:escapeBashArgs '$"'\''({:})''$"'\''({:})'または\$\"\'\(\{:\}\)
  • jcayzacs回答の例:escapeBashArgs x/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"'x/ "b"/aaaaa/'\''xxx yyyy'\''/zz"offf"'またはx/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"

(GNU bash 5.0.3(1)-releaseでテストされています。)


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

3
スーパーユーザーへようこそ!いくつかの説明とコンテキストを提供する長い回答を探しています。1行で答えるのではなく、答えが正しい理由を説明します。理想的には引用を使用します。説明を含まない回答は削除される場合があります。
Gマンは「モニカの復活」を
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.