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


73

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

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

回答:


96

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


"$*"は1つの単語に展開され"$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" ...

これはほとんど常にあなたが望むものです。各定位置パラメーターを個別の単語に展開します。これにより、コマンドラインまたは関数の引数を取得し、別のコマンドまたは関数に渡すのに最適です。また、二重引用符を使用して展開するため"$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内のローカル変数を作成する方法であるkshbashそしてash使いlocalの代わりに)。これはIFS、関数が戻るときに以前の値に復元されることを意味します。これは重要です。これは、IFS標準以外の値に設定されている場合、後で実行するコマンドが適切に動作しない可能性があるためです。


2
素晴らしい説明、どうもありがとう。
ラーム

の使用例をありがとうございます$*。私は常に完全に役に立たないと考えていました...区切り文字との結合は良いユースケースです。
アニシュネ

35

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

"$@"かなり奇妙な構文です。個別のフィールドとして、すべての定位置パラメーターに置き換えられます。いかなる位置パラメータが存在しない場合($#、次いで0される)"$@"1つの位置パラメータがある場合、何も(しない空の文字列が、0の要素を持つリスト)に展開され、その後"$@"に相当する"$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メンテナンスリリースをカウントし、さらに/usr/xpg4/bin/sh安全であるため、#!/bin/shスクリプトのみが影響を受け、#!/usr/bin/env shPATHがPOSIX準拠に設定されている限りスクリプトは影響を受けません) 。つまり、これは歴史的な逸話であり、心配する必要はありません。


"$*"常に1語に展開されます。この単語には、間にスペースを入れて連結した位置パラメータが含まれます。(より一般的には、セパレータの値の最初の文字であるIFS変数。の値が場合はIFS、空の文字列であり、セパレータは空の文字列である。)は、位置パラメータが存在しない場合、"$*"両者が存在する場合、空の文字列であります位置パラメータで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`

1
ヒストリカルノート:いくつかの古代のBourneシェル上で、"$@"実際に空の文字列のconsisitingリストに展開されました:unix.stackexchange.com/questions/68484/...
ninjalj

25

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

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

配列構文では、$*またはを使用しても違いはありません$@。二重引用符"$*""$@"


素晴らしい例!IFS="^${IFS}"ただし、の使用方法を説明できますか?
ラス

@Russ:値がの最初の文字とどのように連結されるかを示しますIFS
クオングルム

それと同じ方法でIFS="^xxxxx"?末尾の${IFS}接尾辞は、最後に元のIFSを何らかの形で自動的に回復する(たとえば、最初の文字が自動的にシフトアウトされるなど)、もっとトリッキーなことをしていると思いました。
ラス

11

指定したコードでも同じ結果が得られます。よりよく理解するには、これを試してください:

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の下にありましたが、それがどのように重要なのかわかりません。


「関数内で変更-グローバルには影響しません」。それをテストしましたか?
ミケル

はい、確認しました。念のため、unset IFS最後にを追加して元に戻しますが、問題なく機能echo $IFSし、標準出力が得られます。IFS中括弧を設定すると、新しいスコープが導入されるため、エクスポートしない限り、外側には影響しませんIFS
Wojtek Rzepala

echo $IFSシェルはを認識するため、何も証明しません,が、IFS!を使用して単語分割を行います。試してくださいecho "$IFS"
ミケル

いい視点ね。設定を解除IFSすると解決します。
Wojtek Rzepala

ない限りは、IFS関数を呼び出す前に、別のカスタム値を持っていました。ただし、ほとんどの場合、IFSの設定解除は機能します。
ミケル

6

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

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

$ 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

つまり、要するに、マルチワードパラメーターを考慮したパラメーターを使用する必要がある場合は、を使用します"$@"


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


2

zshとの違いについて話すbash

周りの引用符で$@$*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 +

引用符がなければ、結果はで同じです$*$@、が異なる中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
zshのでは、$@この点で特別なものではありません:$xに展開高々一つの単語が、空の変数は何も(ない空の単語)に展開されます。試しprint -l a $foo bfoo空または未定義。
ジル

0

答えの1つは$*(私が「スプラット」と考える)はめったに役に立たないと言います。

Googleで検索 G() { IFS='+' ; w3m "https://encrypted.google.com/search?q=$*" ; }

URLは多くの場合で区切られます+が、私のキーボードは   、+ より価値があります。+$*$IFS

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.