Почему $ @ отличается от большинства других переменных в bash? - PullRequest
5 голосов
/ 10 июня 2011

Кажется, что переменная $ @ поддерживает кавычки вокруг своих аргументов, так что, например:

$ function foo { for i in "$@"; do echo $i; done }
$ foo herp "hello world" derp
herp
hello world
derp

Мне также известно, что массивы bash работают так же:

$ a=(herp "hello world" derp)
$ for i in "${a[@]}"; do echo $i; done
herp
hello world
derp

Что на самом деле происходит с такими переменными?Особенно, когда я добавляю в цитату что-то вроде «утка $ {a [@]} гусь»Если это не пробел, то что это?

1 Ответ

11 голосов
/ 10 июня 2011

Обычно двойные кавычки в Bash означают «сделать все, что заключено в кавычки, в одно слово, даже если в нем есть разделители». Но, как вы заметили, $@ ведет себя иначе, когда оно находится в двойных кавычках. На самом деле это хакерский анализ, который восходит к предшественнику Bash, оболочке Bourne, и это специальное поведение применимо только к этой конкретной переменной.

Без этого хака (я использую этот термин, потому что он кажется непоследовательным с точки зрения языка, хотя и очень полезен), для сценария оболочки будет трудно передать свой массив аргументов какой-либо другой команде, которая хочет такие же аргументы , У некоторых из этих аргументов могут быть пробелы, но как бы они передали их другой команде, чтобы оболочка не объединила их в одно большое слово или не обработала список и не разбила аргументы с пробелами?

Ну, вы могли бы передать массив аргументов, и оболочка Bourne действительно имеет только один массив, представленный $* или $@, чье количество элементов равно $#, а элементы $1, $2 и т. Д., Так называемые позиционные параметры.

Пример. Предположим, у вас есть три файла в текущем каталоге с именами aaa, bbb и cc c (в третьем файле есть пробел в имени). Вы можете инициализировать массив (то есть вы можете установить позиционные параметры), чтобы они были именами файлов в текущем каталоге следующим образом:

set -- *

Теперь массив позиционных параметров содержит имена файлов. $#, количество элементов три:

$ echo $#
3

И мы можем перебирать параметры позиции несколькими различными способами.


1) Мы можем использовать $*:

$ for file in $*; do
>    echo "$file"
> done

но это повторно разделяет аргументы в пробеле и вызывает echo четыре раза:

aaa
bbb
cc
c

2) Или мы можем поставить кавычки вокруг $*:

$ for file in "$*"; do
>    echo "$file"
> done

но это группирует весь массив в один аргумент и вызывает echo только один раз:

aaa bbb cc c

3) Или мы могли бы использовать $@, который представляет один и тот же массив, но ведет себя по-разному в двойных кавычках:

$ for file in "$@"; do
>     echo "$file"
> done

будет производить

aaa
bbb
cc c

, поскольку $ 1 = "aaa", $ 2 = "bbb", а $ 3 = "cc c" и "$@" оставляет элементы нетронутыми. Если вы оставите кавычки около $@, оболочка сгладит и повторно проанализирует массив, эхо будет вызвано четыре раза, и вы получите то же самое, что и с голым $*.


Это особенно полезно в сценарии оболочки, где позиционные параметры - это аргументы, которые были переданы вашему сценарию. Чтобы передать те же самые аргументы какой-либо другой команде - без разбиения оболочки на пробельные символы - используйте "$@".

# Truncate the files specified by the args
rm "$@"
touch "$@"

В Bourne это поведение относится только к позиционным параметрам, потому что это действительно единственный массив, поддерживаемый языком. Но вы можете создавать другие массивы в Bash , и вы даже можете применить старый хак разбора к этим массивам, используя специальный синтаксис "${ARRAYNAME[@]}", чей знак at выглядит почти как миг для мистера Борна:

$ declare -a myarray
$ myarray[0]=alpha
$ myarray[1]=bravo
$ myarray[2]="char lie"
$ for file in "${myarray[@]}"; do echo "$file"; done
alpha
bravo
char lie

О, а что касается вашего последнего примера, что должна делать оболочка с "pre $@ post", где у вас есть $@ в двойных кавычках, но у вас есть и другие вещи там? Последние версии Bash сохраняют массив, добавляют текст перед $@ к первому элементу массива и добавляют текст после $@ к последнему элементу:

pre aaa
bb
cc c post
...