次のコードを検討してください。
foo () {
echo $*
}
bar () {
echo $@
}
foo 1 2 3 4
bar 1 2 3 4
それは出力します:
1 2 3 4
1 2 3 4
私はKsh88を使用していますが、他の一般的なシェルにも興味があります。特定のシェルの特殊性を知っている場合は、それらについて言及してください。
SolarisのKsh manページで次のように見つかりました:
$ *と$ @の意味は、引用符で囲まれていない場合、またはパラメーター割り当て値またはファイル名として使用されている場合と同じです。ただし、コマンド引数として使用する場合、$ *は「$ 1d $ 2d ...」と同等です。ここで、dはIFS変数の最初の文字ですが、$ @は$ 1 $ 2 ...と同等です。
IFS
変数を変更してみましたが、出力は変更されません。たぶん私は何か間違ったことをしていますか?
引用されていない場合、$*
と$@
は同じです。スペースまたはワイルドカードを含む引数があるとすぐに予期せず壊れる可能性があるため、これらのいずれも使用しないでください。
"$*"
は単一の単語"$1c$2c..."
に展開されます。通常、c
はスペースですが、実際はIFS
の最初の文字であるため、任意の文字を選択できます。
私がこれまでに見つけた唯一の良い使い方は:
コンマで引数を結合(単純なバージョン)
join1() {
typeset IFS=,
echo "$*"
}
join1 a b c # => a,b,c
指定された区切り文字で引数を結合(より良いバージョン)
join2() {
typeset IFS=$1 # typeset makes a local variable in ksh (see footnote)
shift
echo "$*"
}
join2 + a b c # => a+b+c
"$@"
は別の単語に展開されます:"$1"
"$2"
...
ほとんどの場合、これが必要です。各位置パラメーターを個別のWordに展開します。これにより、コマンドラインまたは関数の引数を受け取り、別のコマンドまたは関数に渡すのに最適です。また、二重引用符を使用して展開されるので、たとえば、"$1"
にスペースまたはアスタリスク(*
)が含まれていても、壊れないことを意味します。
svim
をvim
で実行するSudo
というスクリプトを作成してみましょう。違いを説明するために3つのバージョンを実行します。
svim1
#!/bin/sh
Sudo vim $*
svim2
#!/bin/sh
Sudo vim "$*"
svim3
#!/bin/sh
Sudo vim "$@"
それらのすべては、単純なケースでは問題ありません。スペースを含まない単一のファイル名:
svim1 foo.txt # == Sudo vim foo.txt
svim2 foo.txt # == Sudo vim "foo.txt"
svim2 foo.txt # == Sudo vim "foo.txt"
ただし、複数の引数がある場合は、$*
と"$@"
のみが正しく機能します。
svim1 foo.txt bar.txt # == Sudo vim foo.txt bar.txt
svim2 foo.txt bar.txt # == Sudo vim "foo.txt bar.txt" # one file name!
svim3 foo.txt bar.txt # == Sudo vim "foo.txt" "bar.txt"
スペースを含む引数がある場合は、"$*"
と"$@"
のみが正しく機能します。
svim1 "shopping list.txt" # == Sudo vim shopping list.txt # two file names!
svim2 "shopping list.txt" # == Sudo vim "shopping list.txt"
svim3 "shopping list.txt" # == Sudo vim "shopping list.txt"
したがって、常に正常に機能するのは"$@"
のみです。
typeset
は、ksh
でローカル変数を作成する方法です(bash
およびash
は、代わりにlocal
を使用してください)。これは、関数が戻るときにIFS
が以前の値に復元されることを意味します。 IFS
が標準以外の値に設定されていると、後で実行するコマンドが正しく機能しない可能性があるため、これは重要です。
短い答え:use "$@"
(二重引用符に注意)。他のフォームが役立つことはほとんどありません。
"$@"
はかなり奇妙な構文です。個別のフィールドとして、すべての定位置パラメーターに置き換えられます。位置パラメーターがない場合($#
は0)、"$@"
は何にも展開されません(空の文字列ではなく、要素が0のリスト)。位置パラメーターが1つの場合は"$@"
は"$1"
と同等です。2つの位置パラメータがある場合、"$@"
は"$1" "$2"
と同等です。
"$@"
を使用すると、スクリプトまたは関数の引数を別のコマンドに渡すことができます。これは、ラッパーが呼び出されたときと同じ引数とオプションを使用してコマンドを呼び出す前に、環境変数の設定、データファイルの準備などを行うラッパーに非常に役立ちます。
たとえば、次の関数はcvs -nq update
の出力をフィルタリングします。出力フィルタリングと戻りステータス(grep
ではなくcvs
のステータス)を除いて、一部の引数でcvssm
を呼び出すと、cvs -nq update
を呼び出すように動作しますこれらの引数を使用します。
cvssm () { cvs -nq update "$@" | egrep -v '^[?A]'; }
"$@"
は、定位置パラメーターのリストに展開されます。配列をサポートするシェルには、配列の要素のリストに展開する同様の構文があります:"${array[@]}"
(zshを除いて中括弧は必須です)。繰り返しになりますが、二重引用符は誤解を招く可能性があります。それらはフィールドの分割や配列要素のパターン生成から保護しますが、各配列要素は独自のフィールドになります。
いくつかの古代のシェルには、間違いなくバグがあるものがあります。位置引数がない場合、"$@"
は、フィールドなしではなく、空の文字列を含む単一のフィールドに展開されました。これにより 回避策${1+"$@"}
が作成されました( Perlのドキュメントで有名 になりました)。影響を受けるのは、実際のBourne ShellとOSF1実装の古いバージョンのみであり、互換性のある最新の互換性(ash、ksh、bashなど)は影響を受けません。 /bin/sh
は、私が知っている21世紀にリリースされたシステムには影響しません(Tru64メンテナンスリリースをカウントしない限り、/usr/xpg4/bin/sh
は安全なので、#!/bin/sh
スクリプトのみが影響を受けますPATHがPOSIX準拠に設定されている限り、#!/usr/bin/env sh
スクリプトではありません)。要するに、これはあなたが心配する必要がない歴史の逸話です。
"$*"
は常に1つの単語に展開されます。このWordには、間にスペースが連結された位置パラメータが含まれています。 (より一般的には、セパレーターはIFS
変数の値の最初の文字です。IFS
の値が空の文字列の場合、セパレーターは空の文字列です。)位置パラメータの場合、"$*"
は空の文字列です。2つの位置パラメータがあり、IFS
にデフォルト値がある場合、"$*"
は"$1 $2"
と同等です。
$@
と$*
の外側の引用符は同等です。それらは、"$@"
;のような個別のフィールドとして、位置パラメーターのリストに展開されます。ただし、結果の各フィールドは、通常のように引用符で囲まれていない変数展開でファイル名のワイルドカードパターンとして扱われる個別のフィールドに分割されます。
たとえば、現在のディレクトリに3つのファイルbar
、baz
およびfoo
が含まれている場合、次のようになります。
set -- # no positional parameters
for x in "$@"; do echo "$x"; done # prints nothing
for x in "$*"; do echo "$x"; done # prints 1 empty line
for x in $*; do echo "$x"; done # prints nothing
set -- "b* c*" "qux"
echo "$@" # prints `b* c* qux`
echo "$*" # prints `b* c* qux`
echo $* # prints `bar baz c* qux`
for x in "$@"; do echo "$x"; done # prints 2 lines: `b* c*` and `qux`
for x in "$*"; do echo "$x"; done # prints 1 lines: `b* c* qux`
for x in $*; do echo "$x"; done # prints 4 lines: `bar`, `baz`, `c*` and `qux`
$*
と$@
の違いを示す簡単なスクリプトを次に示します。
#!/bin/bash
test_param() {
echo "Receive $# parameters"
echo Using '$*'
echo
for param in $*; do
printf '==>%s<==\n' "$param"
done;
echo
echo Using '"$*"'
for param in "$*"; do
printf '==>%s<==\n' "$param"
done;
echo
echo Using '$@'
for param in $@; do
printf '==>%s<==\n' "$param"
done;
echo
echo Using '"$@"';
for param in "$@"; do
printf '==>%s<==\n' "$param"
done
}
IFS="^${IFS}"
test_param 1 2 3 "a b c"
出力:
% cuonglm at ~
% bash test.sh
Receive 4 parameters
Using $*
==>1<==
==>2<==
==>3<==
==>a<==
==>b<==
==>c<==
Using "$*"
==>1^2^3^a b c<==
Using $@
==>1<==
==>2<==
==>3<==
==>a<==
==>b<==
==>c<==
Using "$@"
==>1<==
==>2<==
==>3<==
==>a b c<==
配列構文では、$*
または$@
を使用しても違いはありません。二重引用符"$*"
および"$@"
を使用する場合にのみ意味があります。
あなたが提供したコードは同じ結果になります。よりよく理解するには、これを試してください:
foo () {
for i in "$*"; do
echo "$i"
done
}
bar () {
for i in "$@"; do
echo "$i"
done
}
出力は異なるはずです。ここに私が得るものがあります:
$ foo() 1 2 3 4
1 2 3 4
$ bar() 1 2 3 4
1
2
3
4
これはbash
でうまくいきました。私の知る限り、kshはそれほど違わないはずです。基本的に、$*
を引用するとすべてが1つの単語として扱われ、$@
を引用するとリストは個別の単語として扱われます(上記の例を参照)。
IFS
変数を$*
で使用する例として、
fooifs () {
IFS="c"
for i in "$*"; do
echo "$i"
done
unset IFS # reset to the original value
}
結果としてこれが得られます:
$ fooifs 1 2 3 4
1c2c3c4
また、ksh
でも同じように機能することを確認しました。ここでテストされたbash
とksh
は両方ともOSXの下でテストされましたが、それがどれほど重要であるかはわかりません。
位置パラメータを正しい方法で使用する必要があるスクリプトを記述する場合、違いは重要です...
次の呼び出しを想像してください:
$ myuseradd -m -c "Carlos Campderrós" ccampderros
ここには4つのパラメーターしかありません。
$1 => -m
$2 => -c
$3 => Carlos Campderrós
$4 => ccampderros
私の場合、myuseradd
はuseradd
のラッパーであり、同じパラメーターを受け入れますが、ユーザーの割り当てを追加します。
#!/bin/bash -e
useradd "$@"
setquota -u "${!#}" 10000 11000 1000 1100
useradd "$@"
が引用されている$@
の呼び出しに注意してください。これはパラメータを尊重し、useradd
にそのまま送信します。 $@
の引用符を外した場合(または$*
も引用符で囲まれていない場合)、useraddには5パラメータが表示されます。これは、スペースを含む3番目のパラメータが2つに分割されるためです。
$1 => -m
$2 => -c
$3 => Carlos
$4 => Campderrós
$5 => ccampderros
(逆に、"$*"
を使用した場合、useraddは-m -c Carlos Campderrós ccampderros
の1つのパラメーターのみを表示します)
つまり、簡単に言うと、複数のWordパラメータを考慮してパラメータを操作する必要がある場合は、"$@"
を使用します。
* Expands to the positional parameters, starting from one. When
the expansion occurs within double quotes, it expands to a sin‐
gle Word with the value of each parameter separated by the first
character of the IFS special variable. That is, "$*" is equiva‐
lent to "$1c$2c...", where c is the first character of the value
of the IFS variable. If IFS is unset, the parameters are sepa‐
rated by spaces. If IFS is null, the parameters are joined
without intervening separators.
@ Expands to the positional parameters, starting from one. When
the expansion occurs within double quotes, each parameter
expands to a separate Word. That is, "$@" is equivalent to "$1"
"$2" ... If the double-quoted expansion occurs within a Word,
the expansion of the first parameter is joined with the begin‐
ning part of the original Word, and the expansion of the last
parameter is joined with the last part of the original Word.
When there are no positional parameters, "$@" and $@ expand to
nothing (i.e., they are removed).
// man bash 。 ksh、afair、同様の動作です。
zsh
とbash
の違いについて:
$@
と$*
を囲む引用符では、zsh
とbash
は同じように動作し、結果はすべてのシェルで非常に標準的だと思います。
$ f () { for i in "$@"; do echo +"$i"+; done; }; f 'a a' 'b' ''
+a a+
+b+
++
$ f () { for i in "$*"; do echo +"$i"+; done; }; f 'a a' 'b' ''
+a a b +
引用符がなければ、$*
と$@
の結果は同じですが、bash
とzsh
は異なります。この場合、zsh
は奇妙な動作を示します。
bash$ f () { for i in $*; do echo +"$i"+; done; }; f 'a a' 'b' ''
+a+
+a+
+b+
zsh% f () { for i in $*; do echo +"$i"+; done; }; f 'a a' 'b' ''
+a a+
+b+
(Zshは、明示的に要求されない限り、通常、IFSを使用してテキストデータを分割しませんが、リストの空の引数が予期せず欠落していることに注意してください。)
答えの1つは、_$*
_(これは「スプラット」と考える)がほとんど役に立たないことです。
私はG() { IFS='+' ; w3m "https://encrypted.google.com/search?q=$*" ; }
でグーグルを検索します
URLは_+
_で分割されることが多いのですが、私のキーボードは_+
_よりも簡単に到達できるため、_$*
_ + _$IFS
_は価値があると感じます。