web-dev-qa-db-ja.com

Bash引数に引用符を保持する方法は?

渡された引数に引用符を付けたいBashスクリプトがあります。

例:

./test.sh this is "some test"

次に、これらの引数を使用し、引用符と引数リスト全体の引用符を含めて再利用します。

\"$@\"を使用してみましたが、リスト内の引用符が削除されます。

どうすればこれを達成できますか?

68
zlack

"$@"を使用すると、引数を空白文字に再分割せずにリストとして置換します(シェルスクリプトが呼び出されたときに1回分割されました)。別のプログラムへの引数。

何をしようとしていて、どのように機能していないのですか?

80
Chris Dodd

Yukuの答えは、スクリプトの唯一のユーザーである場合にのみ機能しますが、Dennis Williamsonの答えは、主に文字列の印刷に興味があり、引用符がないことを期待している場合に最適です。

すべての引数を1つの大きな引用符付き文字列引数としてbashまたはsuの_-c_パラメーターに渡す場合に使用できるバージョンを次に示します。

_#!/bin/bash
C=''
for i in "$@"; do 
    i="${i//\\/\\\\}"
    C="$C \"${i//\"/\\\"}\""
done
bash -c "$C"
_

したがって、all引数はそれらの周りに引用符を取得します(この目的のために以前に存在していなかった場合は無害です)が、エスケープをエスケープしてから、既に引数にあった引用符をエスケープします(構文_${var//from/to}_はグローバルな部分文字列置換を行います)。

もちろん、すでに空白が含まれているものだけを引用することもできますが、ここでは重要ではありません。このようなスクリプトの1つのユーティリティは、環境変数の特定の定義済みセットを持つことです(またはsuを使用して、すべてを二重引用符で囲むことなく、特定のユーザーとして実行します)。


更新:最近、このスクリプトにつながる最小限のフォークでPOSIXの方法でこれを行う理由がありました(そこの最後のprintfは、スクリプトを呼び出すために使用されるコマンドラインを出力します。これを呼び出すためにコピーアンドペーストすることができますそれと同等の引数):

_#!/bin/sh
C=''
for i in "$@"; do
    case "$i" in
        *\'*)
            i=`printf "%s" "$i" | sed "s/'/'\"'\"'/g"`
            ;;
        *) : ;;
    esac
    C="$C '$i'"
done
printf "$0%s\n" "$C"
_

シェルは_''_- quotes内の_$_や_!!_のようなものも解釈するため、私は_""_に切り替えました。

38
unhammer

空白を含む引数を引用符で囲む必要がある(そして引用符を付ける必要がある)と仮定しても安全な場合は、次のように追加できます。

#!/bin/bash
whitespace="[[:space:]]"
for i in "$@"
do
    if [[ $i =~ $whitespace ]]
    then
        i=\"$i\"
    fi
    echo "$i"
done

サンプルの実行を次に示します。

$ ./argtest abc def "ghi jkl" $'mno\tpqr' $'stu\nvwx'
abc
def
"ghi jkl"
"mno    pqr"
"stu
vwx"

挿入リテラルタブと改行を使用することもできます Ctrl-VTab そして Ctrl-VCtrl-J $'...'内でエスケープを使用する代わりに、二重引用符または単一引用符で囲みます。

Bashの挿入文字に関する注意:BashでViキーバインディング(set -o vi)を使用している場合(Emacsがデフォルト-set -o emacs)、必要です。 挿入モード文字を挿入するためである。 Emacsモードでは、常に挿入モードになります。

32

すべての引数を別のインタプリタに転送するためにこれが必要でした。最終的に私にとって正しいのは:

bash -c "$(printf ' %q' "$@")"

例(forward.shという名前の場合):

$ ./forward.sh echo "3 4"
3 4
$ ./forward.sh bash -c "bash -c 'echo 3'"
3

(もちろん、私が使用する実際のスクリプトはより複雑で、私の場合はNohupやリダイレクトなどが含まれますが、これは重要な部分です。)

7
isarandi

トムヘイルが言ったように、これを行う1つの方法は、printfを使用して%qを引用エスケープします。

例えば:

send_all_args.sh

#!/bin/bash
if [ "$#" -lt 1 ]; then
 quoted_args=""
else
 quoted_args="$(printf " %q" "${@}")"
fi

bash -c "$( dirname "${BASH_SOURCE[0]}" )/receiver.sh${quoted_args}"

send_fewer_args.sh

#!/bin/bash
if [ "$#" -lt 2 ]; then
 quoted_last_args=""
else
 quoted_last_args="$(printf " %q" "${@:2}")"
fi

bash -c "$( dirname "${BASH_SOURCE[0]}" )/receiver.sh${quoted_last_args}"

receiver.sh

#!/bin/bash
for arg in "$@"; do
  echo "$arg"
done

使用例:

$ ./send_all_args.sh
$ ./send_all_args.sh a b
a
b
$ ./send_all_args.sh "a' b" 'c "e '
a' b
c "e 
$ ./send_fewer_args.sh
$ ./send_fewer_args.sh a
$ ./send_fewer_args.sh a b
b
$ ./send_fewer_args.sh "a' b" 'c "e '
c "e 
$ ./send_fewer_args.sh "a' b" 'c "e ' 'f " g'
c "e 
f " g
7
Gary S. Weaver

私の問題は似ていて、ここに投稿されたさまざまなアイデアを使用しました。

電子メールを送信するPHPスクリプトを備えたサーバーがあります。次に、SSH経由で1番目のサーバーに接続して実行する2番目のサーバーがあります。

スクリプト名は両方のサーバーで同じであり、両方とも実際にはbashスクリプトを介して実行されます。

サーバー1(ローカル)bashスクリプトには次のものがあります。

/usr/bin/php /usr/local/myscript/myscript.php "$@"

これは/usr/local/bin/myscriptにあり、リモートサーバーによって呼び出されます。スペースを含む引数に対しても正常に機能します。

ただし、リモートサーバーでは、最初のサーバーが"$@"から引用符を受け取らないため、同じロジックを使用できません。 JohnMuddとDennis Williamsonのアイデアを使用して、オプションとパラメーターの配列を引用符で再作成しました。アイテムにスペースが含まれている場合にのみ、エスケープされた引用符を追加するというアイデアが気に入っています。

そのため、リモートスクリプトは次のコマンドで実行されます。

CSMOPTS=()
whitespace="[[:space:]]"
for i in "$@"
do
    if [[ $i =~ $whitespace ]]
    then
        CSMOPTS+=(\"$i\")
    else
        CSMOPTS+=($i)
    fi
done

/usr/bin/ssh "$USER@$SERVER" "/usr/local/bin/myscript ${CSMOPTS[@]}"

"${CSMOPTS[@]}"を使用して、オプション配列をリモートサーバーに渡すことに注意してください。

ここに投稿したeveyoneに感謝します!本当に助かりました! :)

5
Gus Neves

nhammer の例を配列を使用するように変更しました。

printargs() { printf "'%s' " "$@"; echo; };  # http://superuser.com/a/361133/126847

C=()
for i in "$@"; do
    C+=("$i")  # Need quotes here to append as a single array element.
done

printargs "${C[@]}"  # Pass array to a program as a list of arguments.                               
4
JohnMudd

引用符はbashによって解釈され、コマンドライン引数または変数値には保存されません。

引用符で囲まれた引数を使用する場合は、使用するたびに引用符で囲む必要があります。

val="$3"
echo "Hello World" > "$val"
3
mouviciel

ただ使用する:

"${@}"

例えば:

# cat t2.sh
for I in "${@}"
do
   echo "Param: $I"
done
# cat t1.sh
./t2.sh "${@}"

# ./t1.sh "This is a test" "This is another line" a b "and also c"
Param: This is a test
Param: This is another line
Param: a
Param: b
Param: and also c
1
FrameGrace

すべての引数をbashに渡す必要がある場合別のプログラミング言語から(たとえば、bash -cまたはemit_bash_code | bashを実行する場合)、これを使用します。

  • '\''を使用して、単一引用符をすべてエスケープします。
  • 次に、結果を単一引用符で囲みます

したがって、abc'defの引数は'abc'\''def'に変換されます。文字'\''は次のように解釈されます。既存の引用は最初の最初の引用で終了し、エスケープされた単一の引用\'が来て、新しい引用が開始されます。

0
VasiliNovikov

Gary S. Weaverがソースコードのヒントで示したように、コツはパラメーター '-c'でbashを呼び出してから、次を引用することです。

例えば.

bash -c "<your program> <parameters>"

または

docker exec -it <my docker> bash -c "$SCRIPT $quoted_args"
0
Lars

はい、引用符を保持することは不可能のようですが、私が扱っていた問題のためにそれは必要ありませんでした。

フォルダーを再帰的に検索して文字列をgrepするbash関数があります。問題は、「この文字列を見つける」などのスペースを含む文字列を渡すことです。これをbashスクリプトに渡すと、ベース引数$ nを取得してgrepに渡します。これは、これらが異なる引数であるとgrepが信じています。 bashを引用して関数を呼び出すと、引用内の項目が1つの引数にグループ化されるという事実を使用して、これを解決しました。その引数を引用符で装飾し、grepコマンドに渡す必要がありました。

次のステップに引用符が必要なbashで受け取っている引数がわかっている場合は、引用符で飾ることができます。

0
Tom Orsi