$* 和 $@ 有什麼不同?

$* 和 $@ 有什麼不同?

考慮以下程式碼:

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。除了輸出過濾和返回狀態(是 ofgrep而不是 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:)

因此,簡而言之,如果您需要使用涉及多字參數的參數,請使用"$@".

相關內容