Яка різниця між $ * і $ @?


72

Розглянемо наступний код:

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змінну, але вона не змінює вихід. Може я роблю щось не так?

Відповіді:


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який працює 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встановлено щось нестандартне.


2
Чудове пояснення, дуже дякую.
rahmu

Дякуємо за приклад використання $*. Я завжди вважав це абсолютно марним ... приєднатись до роздільника - це корисний випадок.
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<==

У синтаксисі масиву немає різниці при використанні $*або $@. Це має сенс лише тоді, коли ви використовуєте їх з подвійними цитатами "$*"і "$@".


Відмінний приклад! Чи можете ви пояснити використання IFS="^${IFS}"?
Russ

@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 , але я не можу бачити , як це буде мати особливого значення.


"змінено в межах функції - не впливає на глобальну". Ви тестували це?
Мікель

Так, я це перевірив. Для душевного спокою я додамо unset IFSв кінці, щоб скинути його до оригіналу, але це працювало для мене без проблем, і це echo $IFSпризвело до стандартного виводу, який я отримую від нього. Якщо встановити IFSфігурні дужки, введено нове поле, тому якщо ви не експортуєте їх, це не вплине на зовнішню сторону IFS.
Wojtek Rzepala

echo $IFSнічого не доводить, тому що оболонка бачить ,, але потім робить розділення слів за допомогою IFS! Спробуйте echo "$IFS".
Мікель

влучне зауваження. невдоволення IFSповинно це вирішити.
Wojtek Rzepala

Якщо ви не 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).

// man bash . є ksh, afair, схожа поведінка.


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-адреси часто розділяються на "a" +, але мою клавіатуру   полегшувати, ніж +, $*+ $IFSвідчувати себе варто.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.