$ *和$ @有什么区别?


72

考虑以下代码:

foo () {
    echo $*
}

bar () {
    echo $@
}

foo 1 2 3 4
bar 1 2 3 4

它输出:

1 2 3 4

1 2 3 4

我正在使用Ksh88,但是我也对其他常见的shell感兴趣。如果您碰巧知道特定外壳的任何特殊性,请提及它们。

我在Solaris的Ksh手册页中发现了以下问题:

$ *和$ @的含义在不加引号或用作参数分配值或文件名时相同。但是,当用作命令参数时,$ *等效于``$ 1d $ 2d ...'',其中d是IFS变量的第一个字符,而$ @等效于$ 1 $ 2 ....

我尝试修改IFS变量,但没有修改输出。也许我做错了什么?

Answers:


95

当他们中没有报价,$*并且$@是相同的。您不应该使用任何一个,因为一旦您有包含空格或通配符的参数,它们可能会意外中断。


"$*"扩展为一个单词"$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运行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是如何在中创建局部变量kshbash并改为ash使用local)。这意味着IFS在函数返回时将恢复到其先前的值。这很重要,因为如果IFS将其设置为非标准设置,则随后运行的命令可能无法正常运行。


2
精彩的解释,非常感谢。
rahmu 2012年

感谢您使用的示例$*。我一直认为它是完全没有用的...与定界符结合是一个很好的用例。
阿西萨内(Anishsane)'16

35

简短答案:使用"$@"(注意双引号)。其他形式很少有用。

"$@"是一种很奇怪的语法。它被所有位置参数替换为单独的字段。如果没有位置参数($#为0),则"$@"扩展为无(不是空字符串,而是包含0个元素的列表),如果有一个位置参数则"$@"等于"$1",如果有两个位置参数则"$@"等于"$1" "$2"

"$@"允许您将脚本或函数的参数传递给另一个命令。对于包装器在调用具有与包装器相同的参数和选项的命令之前执行设置环境变量,准备数据文件等操作的包装器非常有用。

例如,以下函数过滤的输出cvs -nq update。除了输出过滤和返回状态(是grep而不是的返回状态cvs)之外,调用cvssm某些参数的行为类似于cvs -nq update使用这些参数进行调用。

cvssm () { cvs -nq update "$@" | egrep -v '^[?A]'; }

"$@"扩展到位置参数列表。在支持数组的shell中,有类似的语法可以扩展到数组的元素列表:("${array[@]}"除了zsh外,花括号是必需的)。同样,双引号在某种程度上具有误导性:它们可以防止字段拆分和数组元素的模式生成,但是每个数组元素最终都位于其自己的字段中。

一些古老的shell可以说是一个bug:没有位置参数时,"$@"将其扩展到包含空字符串的单个字段,而不扩展到没有字段。这导致了解决方法${1+"$@"}通过Perl文档闻名)。仅影响实际的Bourne Shell和OSF1实现的较旧版本,而不会影响其现代兼容的替代版本(ash,ksh,bash等)。/bin/sh在我所知道的21世纪发行的任何系统上都不会受到影响(除非您算出Tru64维护版本,甚至/usr/xpg4/bin/sh安全,因此只#!/bin/sh影响#!/usr/bin/env sh脚本,而不影响脚本,只要您的PATH设置为符合POSIX) 。简而言之,这是您无需担心的历史轶事。


"$*"总是扩展为一个单词。这个词包含位置参数,位置参数之间用空格隔开。(通常,分隔符是IFS变量值的第一个字符。如果的值IFS是空字符串,则分隔符是空字符串。)如果没有位置参数,则"$*"是空字符串,如果有两个位置参数,IFS并且其默认值"$*"等于"$1 $2",等等。

$@$*外部引号是等效的。它们扩展为位置参数列表,作为单独的字段,例如"$@";。但随后将每个结果字段拆分为多个单独的字段,这些字段被视为文件名通配符模式,与平时一样使用未引用的变量扩展名。

例如,如果当前目录包含三个文件barbazfoo,则:

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 shell,"$@"确实扩大为空字符串列表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:它向您显示value与第一个字符的联系方式IFS
cuonglm

用同样的方式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应该不会有太大区别。本质上,$*如上例所示,引用会将所有内容视为一个单词,而引用$@会将列表视为单独的单词。

作为在中使用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下,但我看不出怎么会多大关系。


“在函数内更改-不会影响全局”。你测试了吗?
Mikel 2012年

是的,我已经检查过了。为了让您省心,我将unset IFS在最后添加,将其重置为原始echo $IFS格式,但是它对我来说没有任何问题,并且最终获得了我从中获得的标准输出。设置IFS带有大括号的会引入新的范围,因此,除非导出它,否则不会影响到外部IFS
Wojtek Rzepala 2012年

echo $IFS不会证明任何内容,因为外壳程序看到了,,但随后使用IFS!进行了单词拆分 尝试echo "$IFS"
Mikel 2012年

好点子。设置IFS应该解决。
Wojtek Rzepala 2012年

除非IFS在调用函数之前具有不同的自定义值。但是,是的,大多数时候取消IFS设置都会起作用。
Mikel 2012年

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个参数将被分成两部分:

$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,公平,类似的行为。


2

谈论zsh和之间的区别bash

$@和引号引起来$*zsh并且bash表现相同,我猜结果在所有shell中都非常标准:

 $ 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拆分文本数据,但请注意,此处的空参数意外地在列表中丢失。)


1
在zsh中,$@这方面并不特殊:最多可$x扩展到一个单词,但空变量不能扩展为零(不是空单词)。尝试使用空或未定义。print -l a $foo bfoo
Gilles 2012年

0

答案之一说$*(我认为是“ splat”)很少有用。

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

由于网址通常是用分隔的+,但我的键盘   +$*+ 更容易$IFS取用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.