シェルプロンプトで引数を出力し、シェルプロンプトでそれらを書き込むように引用できますか?


9

シェルスクリプトでは、"$@"必要に応じて引用することで、スクリプトの引数に展開されます。たとえば、これはスクリプトの引数をgccに転送します。

gcc -fPIC "$@"

<<<ただし、bashのpass-to-stdin構文を使用する"@$"と、期待どおりに機能しません。

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

./test.sh foo "bar baz"与えられたスクリプトを呼び出す

foo bar baz

私は期待します

foo "bar baz"

シェルプロンプトでそれらを書くように、その引数を出力するシェルスクリプトを書く方法はありますか?たとえば、ヒントにスクリプト引数を含め、次に使用するコマンドに関するヒント。

回答:


5

さて、"$@"定位置パラメーターのリストに展開されます(定位置パラメーターごとに1つの引数)。

あなたがするとき:

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

cmd空の文字列foo barとの3つの引数で呼び出されますblah<newline>blah。シェルはexecve()次のようなものでシステムコールを呼び出します:

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

同じ呼び出しを再現するシェルコマンドライン(シェル言語のコード)を再構築する場合は、次のようにします。

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 "$@"

またはでzsh、さまざまなタイプの引用符を要求します。

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'

またはzshbashまたはksh93(ここではbash、他のシェルを使用するYMMV):

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

シェルのxtraceオプションを使用して、シェルが実行する内容を出力することもできます。

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

上記では、位置パラメーターを引数として使用して:no-opコマンドを実行しましたcmd。私のシェルは、シェルへの再入力に適した、引用された方法でそれらを印刷しました。すべてのシェルがそうするわけではありません。


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

いいえ、これは起こりません。プログラムを呼び出すと、引数のリストが取得されます。各引数は文字列です。あなたはシェルプログラムを実行すると./test.sh foo "bar baz":、この3つの引数を持つコールを構築し./test.shfoo、とbar baz。(0番目の引数はプログラム名です。これにより、プログラムはどのような名前で呼ばれるかを知ることができます。)引用はシェルの機能であり、プログラム呼び出しの機能ではありません。シェルは、呼び出し時にこのリストを作成します。

"$@"スクリプトまたは関数に渡された引数のリストを、それが使用されている呼び出しの引数のリストに直接コピーします。これらのリストではシェルの解析が行われないため、引用は含まれません。

ではcat <<< "$@""$@"単一の文字列が必要なコンテキストで使用しています。<<<operator`は、文字列ではなく、文字列のリストを必要とします。このコンテキストでは、bashはリストの要素を取り、それらの間にスペースを入れて結合します。

スクリプトデバッグの場合、実行するとset -xset +xオフにするため)、トレースモードがアクティブになり、各コマンドが実行される前に出力されます。bashでは、そのトレースに引用符が含まれており、コマンドをシェルに貼り付けることができます(これはすべてのsh実装に当てはまるわけではありません)。

文字列があり、それを解析して元の文字列に戻すシェルソース構文に変換する場合は、文字列を単一引用符で囲み、文字列内のすべての単一引用符をで置き換えることができます'\''

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

文字列置換構文はksh93 / bash / zsh / mksh固有です。単純なshでは、文字列をループする必要があります。

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

"$@" スクリプト引数に展開し、必要に応じて引用します

まあ、ちょっと。実用的な目的のために、十分に近いはずであり、リファレンスマニュアルには、"$@" is equivalent to "$1" "$2" ...

したがって、2つのパラメータfooとを使用するとbar baz、これらは同様になります。

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

(パラメータではなく、単なる文字列の特殊文字が含まれている場合、彼らは展開した後、再び拡大されないことを除いて$@$1...)

しかし$@、引用符で囲まれたパラメーターで置き換えることを検討したとしても、引用符はecho表示されgccません。同様に、引用符も取得されません。

<<<"$@"== "$1" "$2" ...ルールのちょっとした例外ですが、パラメーターと変数の展開、および引用符の削除などを行った後で、そのことが明示的に述べられThe result is supplied as a single string to the command on its standard inputています。いつものように、入力として<<< "foo"与えるだけfooで、同じように引数としてsomecmd "foo"与えるだけfooです。

スクリプトを./test.sh foo "bar baz"[...] として呼び出すfoo "bar baz"

相場が残っている場合、それはまだでなければなりません"foo" "bar baz"。シェルまたは実行中のコマンドは、コマンドが実行されたときの引用が何であったかを理解していません。あるいは、引用する引用があったとしても、システムコールはnullで終了する文字列のリストを受け取るだけであり、引用はシェル言語の機能にすぎません。他の言語には他の規約があるかもしれません。


0

bashの代替ソリューション

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

Bashはネストされた置換をサポートしていないため、配列の再割り当て方法を示す/programming/12303974/assign-array-to-variable#12304017に感謝します。参照man bashhttps://linux.die.net/man/1/bashアレイ、拡張、及び(パラメータ展開下)パターン置換の詳細については)。

分析

bashはコマンドラインパラメータを配列として配置します $@

q 引用文字を保持します。

パラメータ展開を二重引用符で囲むと${ ... }、個々のパラメータが個別の要素として保持され、それらをラップ( )して、配列を変数に割り当てることができます。

/#/$qパラメータ展開では、パターンの始まり(regexなど^)を引用文字で置き換えます。

/%/$qパラメータ展開では、パターンの終わり(regexなど$)を引用文字で置き換えます。

ユースケース:コマンドラインからMySQLにクエリしてメールアドレスのリストを取得する

上記のステートメントにいくつかの変更があり、異なる引用文字を使用し、パラメーターの間にコンマを追加し、最後のコンマを取り除きます。そしてもちろん、mysqlの呼び出しにパスワードを入力するのが苦手です。だから私を訴えます。

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

二重引用符での引用は、バックスラッシュ、- $展開、およびその他の二重引用符の保護にはあまり役立ちません。そのため、他の回答では、文字列の一重引用符を処理するためにある程度の長さを取りながら一重引用符を使用するか、文字列の引用符付きコピーを生成するためにシェル独自の機能を使用します。
ilkkachu 2018年

@ilkkachuちゃんと指摘しました!そのため、(他の理由とともに)以前のすべての回答に賛成しました。また、なぜこのユースケースを追加したのか。うまくいけば、完璧は善の敵ではない。
Jeff
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.