web-dev-qa-db-ja.com

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

次のコードを検討してください。

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変数を変更してみましたが、出力は変更されません。たぶん私は何か間違ったことをしていますか?

92
rahmu

引用されていない場合、$*$@は同じです。スペースまたはワイルドカードを含む引数があるとすぐに予期せず壊れる可能性があるため、これらのいずれも使用しないでください。


"$*"は単一の単語"$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"にスペースまたはアスタリスク(*)が含まれていても、壊れないことを意味します。


svimvimで実行する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が標準以外の値に設定されていると、後で実行するコマンドが正しく機能しない可能性があるため、これは重要です。

114
Mikel

短い答え: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つのファイルbarbazおよび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<==

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

27
cuonglm

あなたが提供したコードは同じ結果になります。よりよく理解するには、これを試してください:

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でも同じように機能することを確認しました。ここでテストされたbashkshは両方ともOSXの下でテストされましたが、それがどれほど重要であるかはわかりません。

11
Wojtek

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

次の呼び出しを想像してください:

$ myuseradd -m -c "Carlos Campderrós" ccampderros

ここには4つのパラメーターしかありません。

$1 => -m
$2 => -c
$3 => Carlos Campderrós
$4 => ccampderros

私の場合、myuseradduseraddのラッパーであり、同じパラメーターを受け入れますが、ユーザーの割り当てを追加します。

#!/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パラメータを考慮してパラメータを操作する必要がある場合は、"$@"を使用します。

6
   *      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、同様の動作です。

4
rush

zshbashの違いについて:

$@$*を囲む引用符では、zshbashは同じように動作し、結果はすべてのシェルで非常に標準的だと思います。

 $ 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 +

引用符がなければ、$*$@の結果は同じですが、bashzshは異なります。この場合、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を使用してテキストデータを分割しませんが、リストの空の引数が予期せず欠落していることに注意してください。)

2

答えの1つは、_$*_(これは「スプラット」と考える)がほとんど役に立たないことです。

私はG() { IFS='+' ; w3m "https://encrypted.google.com/search?q=$*" ; }でグーグルを検索します

URLは_+_で分割されることが多いのですが、私のキーボードは_+_よりも簡単に到達できるため、_$*_ + _$IFS_は価値があると感じます。

0
isomorphismes