考虑以下代码:

foo () {
    echo $*
}

bar () {
    echo $@
}

foo 1 2 3 4
bar 1 2 3 4


其输出:


1 2 3 4

1 2 3 4


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

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


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


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

#1 楼

如果不引用它们,则$*$@相同。您不应该使用其中任何一个,因为一旦您的参数包含空格或通配符,它​​们可能会意外中断。


"$*"扩展为单个单词"cc..."。通常c是一个空格,但实际上它是IFS的第一个字符,因此可以选择任何东西。

我发现的唯一好的用法是:

使用逗号连接参数(简单版本)

join1() {
    typeset IFS=,
    echo "$*"
}

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


使用指定的分隔符连接参数(更好的版本)

join2() {
    typeset IFS=   # typeset makes a local variable in ksh (see footnote)
    shift
    echo "$*"
}

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



"$@"扩展为单独的单词:"" "" ...

这几乎总是您想要的。它将每个位置参数扩展为一个单独的单词,非常适合将命令行或函数参数传入,然后将其传递给另一个命令或函数。并且由于它使用双引号扩展,因此如果""包含空格或星号(*),则表示东西不会中断。让我们编写一个名为svim的脚本,该脚本可以运行vimsudo。我们将使用三个版本来说明它们之间的区别。

>
svim1

#!/bin/sh
sudo vim $*


对于简单的情况,所有这些都很好,例如一个不包含空格的文件名:

#!/bin/sh
sudo vim "$*"


,但是如果您有多个参数,则只有svim2svim3才能正常工作。

如果您的参数包含空格,则只有$*"$@"可以正常工作。

#!/bin/sh
sudo vim "$@"


所以只有"$*"可以一直正常工作。 br />

"$@"是如何在"$@"中创建局部变量(typesetksh使用bash代替)。这意味着当函数返回时,ash将恢复为其先前的值。这很重要,因为如果local设置为非标准设置,则随后运行的命令可能无法正常工作。

评论


精彩的解释,非常感谢。

– rahmu
2012年6月26日15:29

感谢您使用$ *的示例。我一直认为它是完全没有用的...与定界符结合是一个很好的用例。

–二十烷
16年3月31日下午5:01

Bash参考手册:gnu.org/savannah-checkouts/gnu/bash/manual/…

–罗兰
20-2-20在8:15



#2 楼

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

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

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

例如,以下内容函数过滤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等)。在我所知道的21世纪发行的任何系统上,/bin/sh均不受影响(除非您算出Tru64维护版本,即使/usr/xpg4/bin/sh都是安全的,因此只要设置了PATH,#!/bin/sh脚本便会受到影响,而#!/usr/bin/env sh脚本则不受影响。符合POSIX)。简而言之,这是您无需担心的历史轶事。




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

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

例如,如果当前目录包含三个文件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`


评论


历史记录:在某些古老的伯恩贝壳上,“ $ @”确实扩展为包含空字符串的列表:unix.stackexchange.com/questions/68484/…

– ninjalj
13-10-9 13:01

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


在数组语法中,使用$*$@没有区别。仅当使用双引号"$*""$@"时才有意义。

评论


很好的例子!不过,您能解释一下IFS =“ ^ $ {IFS}”的用法吗?

–俄罗斯
16-09-16在12:47

@Russ:它向您展示了IFS中第一个字符与值的联系方式。

–cuonglm
16 Sep 16 '13:03

以与IFS =“ ^ xxxxx”相同的方式进行吗?尾随的$ {IFS}后缀使我觉得您在做些棘手的事情,例如以某种方式在末尾自动恢复原始IFS(例如:第一个字符自动移出或其他原因)。

–俄罗斯
16 Sep 16 '14:48



#4 楼

您提供的代码将产生相同的结果。为了更好地理解它,请尝试以下操作:

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

fooifs () {
    IFS="c"            
    for i in "$*"; do
        echo "$i"
    done
    unset IFS          # reset to the original value
}


我得到的结果是:

$ fooifs 1 2 3 4
1c2c3c4


我也刚刚确认它在IFS中工作相同。在这里测试的$*ksh都在OSX下进行,但我看不出这有多重要。

评论


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

– Mikel
2012年6月25日15:11

是的,我已经检查过了。为了让您省心,我将在最后添加未设置的IFS以将其重置为原始IFS,但这对我来说没有任何问题,并且执行echo $ IFS可以得到我从中获得的标准输出。使用大括号设置IFS会引入一个新范围,因此,除非导出它,否则不会影响外部IFS。

– Wojtek
2012年6月25日16:00



echo $ IFS不会证明任何内容,因为外壳程序会看到,但随后会使用IFS进行单词拆分!尝试回显“ $ IFS”。

– Mikel
2012年6月25日下午16:43

好点子。设置IFS即可解决该问题。

– Wojtek
2012年6月25日17:39

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

– Mikel
2012年6月25日20:51

#5 楼

在编写应以正确方式使用位置参数的脚本时,区别很重要...

想象一下以下调用:这里只有4个参数:在我的情况下,myuseradd只是useradd的包装,该包装接受相同的参数,但为用户增加了配额:

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


请注意对useradd "$@"的调用,并引用$@。这将尊重参数并将它们原样发送给useradd。如果要取消对$@的引用(或也使用未引用的$*),则useradd将看到5个参数,因为包含空格的第三个参数将被分成两部分:

/>(相反,如果您使用"$*",则useradd只会看到一个参数:-m -c Carlos Campderrós ccampderros

因此,简而言之,如果您需要使用尊重多字参数的参数,请使用"$@"

#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 "cc...", 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 ""
          ""  ...   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).


//男士bash。是ksh,公平,类似的行为。

#7 楼

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

评论


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

–吉尔斯'所以-不再是邪恶的'
2012年6月25日23:27

#8 楼

文件名:try
#!/bin/bash

star() {
    echo
    echo '--- $* no quotes'
    for Field in $*; do
        echo $Field
    done
}

star_quote() {
    echo
    echo '--- $* with quotes'
    for Field in "$*"; do
        echo $Field
    done
}

dollar() {
    echo
    echo '--- $@ no quotes'
    for Field in $@; do
        echo $Field
    done
}

dollar_quote() {
    echo
    echo '--- $@ with quotes'
    for Field in "$@"; do
        echo $Field
    done
}


#-----------------------

echo
star $*
star_quote "$*"
dollar $@
dollar_quote "$@"
echo

exit

命令:
./try 1 2 3 "4 and 5"

结果:
 --- $* no quotes
1
2
3
4
and
5

--- $* with quotes
1 2 3 4 and 5

--- $@ no quotes
1
2
3
4
and
5

--- $@ with quotes
1
2
3
4 and 5

 


评论


您应该执行echo“ $ Field”,否则您的测试可能会因使用不同的参数而产生一些令人惊讶的结果。

–muru
20年6月21日在15:00

#9 楼

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

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

因为URL经常被+分割,但我的键盘使+更容易触及,$* + $IFS值得。