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

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

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

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

参照されず、$*同じ$@場合。パラメータにスペースやワイルドカードが含まれると、予期せず中断される可能性があるため、これらの項目を使用しないでください。


"$*"単一の単語に展開します"$1c$2c..."cはBourneシェルの空白ですが、IFS現代のBourne様シェル(kshに由来し、POSIXでshとして指定)の最初の文字なので、必要に応じて指定できます。

私が見つけた唯一の良い用途は次のとおりです。

パラメータをカンマで連結する(簡易版)

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を含めても問題は発生しません。


違いを説明するために3つのバージョンを作成しsvimましょう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"

したがって、この方法では"$@"常に正常に動作できます。


一部のシェルでは注意が必要ですが、マルチバイト文字では機能しません。

²typeset変数の種類と属性を設定し、変数4を変数にローカルksh変数にするために使用されます(ksh93では、function f {}Bourne構文を使用して定義された関数ではなく、Korn構文をf() ...使用して定義された関数にのみ適用されます)。つまりIFS、関数が返されると以前の値が復元されます。IFS設定が標準ではなく、一部の拡張子を引用することを忘れた場合は、後で実行するコマンドが期待どおりに機能しない可能性があるため、これは重要です。

3最初の項目がバックスラッシュで始まる場合、またはバックスラッシュが含まれている場合、引数を正しく印刷または印刷できないことがありますecho。バックスラッシュ処理に(または)オプションの区切り文字を使用せずに(または)で始まるまたは始まる引数を防ぐように指示することができます。オプションの区切り記号。標準的な選択肢になりますが、ksh88とpdksh、およびいくつかの派生物にはまだ組み込まれていません。-print-r-+---printf '%s\n' "$*"printf

4つの空白文字が含まれていない場合、"$@"Bourneシェルとksh88では正しく機能せず、$IFS実際には「引用符なし」スペースに関連付けられた位置引数として実装され、結果が分割されることに注意してください$IFS。 Bourneシェルの初期バージョンには、"$@"位置引数がない場合に空の引数に拡張されるバグがあり、これが時々このバグが表示される理由です${1+"$@"}"$@"これらのバグのどれも最新のBourne様シェルには影響しません。

5 Almquistシェルとboshそのlocal代替。bash、エイリアス(bashとzshにもあります)yashzshありますが、関数でのみ使用できることに注意してください。typesetlocaldeclarebashlocal

答え2

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

"$@"やや奇妙な構文です。これは、別のフィールドであるすべての位置パラメータに置き換えられます。位置引数がない場合($#is 0)、"$@"何もないものに展開されます(空の文字列ではなく要素が0のリスト)。これは、位置引数が1つある場合と"$@"同じ、または"$1"位置引数が2つある場合はETC"$@"と同じです。"$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メンテナンスリリースを含まず、/usr/xpg4/bin/shセキュリティリリースもあるため、スクリプト#!/bin/shのみが影響を受け、#!/usr/bin/env shPATHがPOSIX準拠に設定されている限り、スクリプトは影響を受けません)。とにかく心配する必要のない歴史的な逸話があります。


"$*"常に単語に展開されます。この単語にはスペースで連結された位置パラメータが含まれています。 (より一般的には、区切り文字は変数valueの最初の文字ですIFS。値がIFS空の文字列の場合、区切り文字は空の文字列です。)区切り文字は位置引数がない"$*"場合は空の文字列ですIFS。 etc."$*"と同じです。"$1 $2"

$@外部引用符と$*同じです。たとえば、別のフィールドとして位置引数リストに展開されますが、"$@"各結果フィールドは通常、引用符で囲まれていない変数に展開されるように、ファイル名ワイルドカードパターンとして扱われる別々のフィールドに分割されます。

たとえば、現在のディレクトリにbarbazおよび 3 つのファイルが含まれている場合、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`

答え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<==

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

答え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。)

つまり、複数の単語で構成されるパラメータを含むパラメータを使用する必要がある場合は、を使用します"$@"

関連情報