$* と $@ の違いは何ですか?

$* と $@ の違いは何ですか?

次のコードを考えてみましょう。

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 マニュアル ページで次の記述を見つけました。

$* と $@ の意味は、引用符で囲まれていない場合、またはパラメータ割り当て値やファイル名として使用されている場合は同じです。ただし、コマンド引数として使用される場合、$* は ``$1d$2d...'' と同等です (d は IFS 変数の最初の文字)。一方、$@ は $1 $2 ... と同等です。

変数を変更してみましたIFSが、出力は変更されません。何か間違っているのでしょうか?

答え1

引用符で囲まれておらず、同じである場合$*$@スペースやワイルドカードを含む引数があるとすぐに予期せず動作が中断される可能性があるため、どちらも使用しないでください。


"$*"1 つの単語に展開されます"$1c$2c..."。. cは Bourne シェルではスペースでしたが、IFS最近の Bourne 系シェル (ksh から派生し、sh については POSIX で指定) では の最初の文字になっているため、任意のもの¹ にすることができます。

私がこれまでに見つけた唯一の良い使い方は次のとおりです。

引数をコンマで結合する(簡易版)

function join1 {
    typeset IFS=,      # typeset makes a local variable in ksh²
    print -r -- "$*"   # using print instead of unreliable echo³
}

join1 a b c   # => a,b,c

指定された区切り文字で引数を結合する(より良いバージョン)

function join2 {
    typeset IFS="$1"
    shift
    print -r -- "$*"
}

join2 + a b c   # => a+b+c

"$@"単語を区切るために展開されます:"$1" "$2" ...

これはほとんどの場合、必要なことです。各位置パラメータを個別の単語に展開するため、コマンド ラインまたは関数の引数を取得して別のコマンドまたは関数に渡すのに最適です。また、二重引用符を使用して展開するため、たとえば"$1"スペースやアスタリスク ( *) 4が含まれていても、動作が中断されることはありません。


svim実行されるというスクリプトを記述してみましょう。違いを説明するために 3 つのバージョンを作成します。vimsudo

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"

したがって、"$@"常に適切に動作します。


¹ ただし、一部のシェルではマルチバイト文字には機能しないので注意してください。

²は変数の型と属性を設定するために使用され、 4typesetでは変数をローカルにします(ksh93 では、これは Korn構文で定義された関数のみで、Bourne構文では適用されません)。つまり、関数が返されるときに、ここは以前の値に復元されます。これは重要です。なぜなら、 が非標準に設定され、展開の一部を引用符で囲むのを忘れた場合、後で実行するコマンドが期待どおりに動作しない可能性があるためです。kshfunction f {}f() ...IFSIFS

³ はecho、最初の-引数が で始まるか、いずれかの引数にバックスラッシュが含まれている場合、引数を適切に印刷できないか失敗する可能性printがあります。 でバックスラッシュ処理を行わないように指示したり、 で始まる引数や(または) オプション区切り文字で-r始まる引数に対して保護したりすることができます。は標準的な代替手段ですが、ksh88 と pdksh およびその派生の一部にはまだ組み込みがないことに注意してください。-+---printf '%s\n' "$*"printf

4 は、スペース文字が含まれていない"$@"場合、Bourne シェルと ksh88 では正しく動作しないことに注意してください。これは、位置パラメータが「引用符で囲まれていない」スペースで結合され、結果が分割されるものとして実装されていたためです。Bourne シェルの初期バージョンにも、位置パラメータがない場合に 1 つの空の引数に展開されるバグがありました。これが、の代わりにが時々表示される理由の 1 つです。これらのバグはどちらも、最近の Bourne のようなシェルには影響しません。$IFS$IFS"$@"${1+"$@"}"$@"

5 Almquist シェルでは、代わりに と をbosh使用します。 、およびも使用します。 は、 (bash と zsh では にも)にエイリアスされますが、 では、は関数内でのみ使用できるという注意点があります。localbashyashzshtypesetlocaldeclarebashlocal

答え2

短い答え:使用"$@"(二重引用符に注意してください)。他の形式はほとんど役に立ちません。

"$@"はかなり奇妙な構文です。これは、すべての位置パラメータによって、別々のフィールドとして置き換えられます。位置パラメータがない場合 ($#が 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 シェルと OSF1 実装の古いバージョンのみで、最新の互換性のある代替品 (ash、ksh、bash など) は影響を受けません。/bin/sh私の知る限り、21 世紀にリリースされたどのシステムでも影響を受けません (Tru64 メンテナンス リリースは除きます。Tru64 にも/usr/xpg4/bin/shsafe があるため、 PATH が POSIX 準拠に設定されている限り#!/bin/sh、スクリプトのみが影響を受け、#!/usr/bin/env shスクリプトは影響を受けません)。つまり、これは心配する必要のない歴史的な逸話です。


"$*"は常に 1 つの単語に展開されます。この単語には、間にスペースを挟んで連結された位置パラメータが含まれます。(より一般的には、区切り文字は変数の値の最初の文字ですIFS。 の値がIFS空の文字列の場合、区切り文字は空の文字列です。) 位置パラメータがない場合、 は空の文字列です。"$*"位置パラメータが 2 つあり、 がIFSデフォルト値を持つ場合、は など"$*"と同等になります。"$1 $2"

$@および$*外側の引用符は同等です。これらは、; のように、位置パラメータのリストに個別のフィールドとして展開されます"$@"が、結果の各フィールドは個別のフィールドに分割され、引用符で囲まれていない変数展開で通常行われるように、ファイル名のワイルドカード パターンとして扱われます。

たとえば、現在のディレクトリにbar、の 3 つのファイルが含まれている場合はbazfoo次のようになります。

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`

答え3

$*以下は、との違いを示す簡単なスクリプトです$@

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

$*配列構文では、または を使用する場合に違いはありません。 二重引用符と と$@一緒に使用する場合にのみ意味があります。"$*""$@"

答え4

位置パラメータを正しく使用する必要があるスクリプトを記述する場合、この違いは重要です...

次の通話を想像してください。

$ 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 は 1 つのパラメータのみを参照します: -m -c Carlos Campderrós ccampderros)

つまり、複数単語のパラメータを尊重するパラメータを操作する必要がある場合は、 を使用します"$@"

関連情報