В чем разница между $ * и $ @?

73

Рассмотрим следующий код:

foo () {
    echo $*
}

bar () {
    echo $@
}

foo 1 2 3 4
bar 1 2 3 4

Это выводит:

1 2 3 4

1 2 3 4

Я использую Ksh88, но меня интересуют и другие распространенные оболочки. Если вы знаете какие-либо особенности конкретных оболочек, пожалуйста, укажите их.

Я нашел следующее в справочной странице Ksh по Solaris:

Значение $ * и $ @ одинаково, когда они не заключены в кавычки или используются в качестве значения параметра или имени файла. Однако при использовании в качестве аргумента команды $ * эквивалентно `` $ 1d $ 2d ... '', где d - первый символ переменной IFS, тогда как $ @ эквивалентно $ 1 $ 2 ....

Я попытался изменить IFSпеременную, но она не изменяет вывод. Может я что то не так делаю?

rahmu
источник

Ответы:

96

Когда они не указаны, $*и $@являются одинаковыми. Вы не должны использовать ни один из них, потому что они могут неожиданно прерваться, как только у вас появятся аргументы, содержащие пробелы или символы подстановки.


"$*"расширяется до одного слова "$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. Мы сделаем три версии, чтобы проиллюстрировать разницу.

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как сделать локальную переменную в ksh( bashи ashиспользовать localвместо). Это означает, IFSчто будет восстановлено его предыдущее значение, когда функция вернется. Это важно, потому что команды, которые вы запускаете позже, могут не работать должным образом, если IFSустановлено что-то нестандартное.

Mikel
источник
2
Замечательное объяснение, большое спасибо.
Рахму
Спасибо за пример использования $*. Я всегда считал, что это совершенно бесполезно ... соединение с разделителем - хороший вариант использования.
Anishsane
35

Краткий ответ: используйте"$@" (обратите внимание на двойные кавычки). Другие формы очень редко полезны.

"$@"довольно странный синтаксис. Он заменяется всеми позиционными параметрами, как отдельные поля. Если позиционных параметров нет ( $#равен 0), то "$@"расширяется до нуля (не пустой строки, а списка с 0 элементами), если есть один позиционный параметр, то "$@"эквивалентно "$1", если есть два позиционных параметра, то "$@"эквивалентно "$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 shсценарии, если ваш PATH настроен на соответствие POSIX) , Короче говоря, это исторический анекдот, о котором вам не нужно беспокоиться.


"$*"всегда расширяется до одного слова. Это слово содержит позиционные параметры, соединенные пробелом между ними. (В более общем случае разделитель - это первый символ значения IFSпеременной. Если значение IFS- пустая строка, разделитель - пустая строка.) Если позиционных параметров нет, то "$*"это пустая строка, если есть два позиционные параметры и IFSимеет значение по умолчанию, то "$*"есть эквивалентное "$1 $2"и т. д.

$@и $*внешние кавычки эквивалентны. Они расширяются до списка позиционных параметров, как отдельные поля, например "$@"; но каждое результирующее поле затем разделяется на отдельные поля, которые обрабатываются как шаблоны подстановочных знаков имени файла, как обычно с расширениями переменных без кавычек.

Например, если текущий каталог содержит три файла bar, bazа 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
Историческая справка: на некоторых древних оболочках Борна "$@"действительно расширился список, состоящий из пустой строки: 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<==

В синтаксисе массива нет разницы при использовании $*или $@. Это имеет смысл только тогда, когда вы используете их с двойными кавычками "$*"и "$@".

cuonglm
источник
Отличный пример! Можете ли вы объяснить использование IFS="^${IFS}"?
Расс
@Russ: показывает, как значение соответствует первому символу в 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. И то, bashи другое было kshпротестировано здесь под OSX, но я не понимаю, как это будет иметь большое значение.

Войтек Жепала
источник
msgstr "изменено внутри функции - не влияет на глобальное". Вы проверяли это?
Микель
Да, я проверил это. Для спокойствия я добавлю unset IFSв конце, чтобы сбросить его до оригинала, но у меня это не сработало, и echo $IFSя получил стандартный вывод, который я получаю из него. Установка IFSфигурных скобок вводит новую область видимости, поэтому, если вы не экспортируете ее, это не повлияет на внешнюю сторону IFS.
Войтек Жепала
echo $IFSничего не доказывает, потому что оболочка видит ,, но затем делит слова, используя IFS! Попробуй echo "$IFS".
Микель
хорошая точка зрения. unsetting IFSдолжен решить это.
Войтек Жепала
Если 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-й параметр, содержащий пробел, будет разделен на две части:

$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).

// человек Баш . Кш, издалека, похожее поведение.

порыв
источник
2

Говоря о различиях между zshи bash:

С кавычками $@и $*, zshи bashведут себя одинаково, и я думаю, что результат является вполне стандартным среди всех оболочек:

 $ 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 bс fooпустым или неопределенным.
Жиль "ТАК - перестань быть злым"
0

В одном из ответов говорится $*(что я называю «сплатом») редко бывает полезно.

Я ищу в Google с G() { IFS='+' ; w3m "https://encrypted.google.com/search?q=$*" ; }

Так как URL, часто делится с +, но моя клавиатура делает   легче достичь , чем +, $*+ $IFSчувствовать себя полезным.

isomorphismes
источник