web-dev-qa-db-ja.com

シェルスクリプトで引数を出力できますか?

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

gcc -fPIC "$@"

Bash pass-to-stdin構文<<<を使用する場合、"@$"は期待どおりに機能しません。

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

./test.sh foo "bar baz"でスクリプトを呼び出すと、

foo bar baz

私は期待します

foo "bar baz"

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

9
Alex Jasmin

そうですね、_"$@"_は、位置パラメータごとに1つの引数である位置パラメータのリストに展開されます。

あなたがするとき:

_set '' 'foo bar' $'blah\nblah'
cmd "$@"
_

cmdは、空の文字列_foo bar_と_blah<newline>blah_の3つの引数で呼び出されています。シェルは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'
_

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

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

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

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

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

スクリプトのデバッグでは、set -x(オフにするにはset +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

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

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

したがって、2つのパラメーターfoobar 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で終了する文字列のリストを受け取るだけであり、引用はシェル言語の機能にすぎません。他の言語には他の規約があるかもしれません。

2
ilkkachu

Bashの代替ソリューション

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

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

分析

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

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

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

パラメータ展開の/#/$qは、パターンの先頭(正規表現^など)を引用文字で置き換えます。

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

ユースケース:コマンドラインから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
0
Jeff