$* 和 $@ 有什么区别?

$* 和 $@ 有什么区别?

考虑以下代码:

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

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


"$*"扩展为单个单词"$1c$2c..."c是 Bourne shell 中的一个空格,但现在是IFS现代类似 Bourne shell 中的第一个字符(来自 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,事情不会中断。


让我们编写一个名为 的脚本,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"

所以只有这样"$@"才能一直正常工作。


尽管在某些 shell 中要小心,但它不适用于多字节字符。

²typeset用于设置变量的类型和属性,也使变量在ksh4中成为本地变量(在 ksh93 中,这仅适用于使用 Kornfunction f {}语法定义的函数,不适用于使用 Bournef() ...语法定义的函数)。这意味着IFS当函数返回时,这里将恢复到之前的值。这很重要,因为如果IFS设置为非标准并且您忘记引用一些扩展,您之后运行的命令可能无法按预期工作。

如果第一个以反斜杠开头或任何包含反斜杠,则³echo将或可能无法正确打印其参数,可以被告知不要使用 (或 ) 选项分隔符进行反斜杠处理并防止参数开头或以(或) 选项分隔符开头。将是标准替代方案,但请注意 ksh88 和 pdksh 及其一些衍生产品仍然没有内置。-print-r-+---printf '%s\n' "$*"printf

4请注意,当不包含空格字符时,它"$@"在 Bourne shell 和 ksh88 中无法正常工作$IFS,实际上它是作为位置参数与“不带引号的”空格连接而实现的,并且结果会被$IFS分割。 Bourne shell 的早期版本也存在这样的错误:"$@"当没有位置参数时,该错误会扩展为一个空参数,这就是为什么您有时会${1+"$@"}看到"$@".这些错误都不影响现代的类似 Bourne 的 shell。

5 Almquist shell 并boshlocal其替代。bashyash并且zsh还有typeset, 别名local(也在declarebash 和 zsh 中),但需要注意的是bash,local只能在函数中使用。

答案2

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

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

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

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

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

"$@"展开为位置参数列表。在支持数组的 shell 中,有一个类似的语法来扩展数组的元素列表:("${array[@]}"除了 zsh 之外,大括号是强制的)。同样,双引号有些误导:它们可以防止数组元素的字段分割和模式生成,但每个数组元素最终都在其自己的字段中。

一些古老的 shell 存在一个可以说是错误的问题:当没有位置参数时,"$@"扩展为包含空字符串的单个字段,而不是没有字段。这导致了解决方法${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有其默认值 then"$*"相当于 等"$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`

答案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参数,因为包含空格的第三个参数将被分成两部分:

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

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

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

相关内容