Удивительное поведение расширения массива

Я был удивлен линией, отмеченной (!!) в следующем примере:

log1 () { echo [email protected]; }
log2 () { echo "[email protected]"; }

X=(a b)
IFS='|'

echo ${X[@]}   # prints a b
echo "${X[@]}" # prints a b
echo ${X[*]}   # prints a b
echo "${X[*]}" # prints a|b
echo "---"
log1 ${X[@]}   # prints a b
log1 "${X[@]}" # prints a b
log1 ${X[*]}   # prints a b
log1 "${X[*]}" # prints a b (!!)
echo "---"
log2 ${X[@]}   # prints a b
log2 "${X[@]}" # prints a b
log2 ${X[*]}   # prints a b
log2 "${X[*]}" # prints a|b

Вот мое понимание поведения:

  • ${X[*]} и ${X[@]} расширяются до a b
  • "${X[*]}" расширяется до "a|b"
  • "${X[@]}" расширяется до "a" "b"
  • $* и [email protected] имеют то же поведение, что и ${X[*]} и ${X[@]}, за исключением того, что их содержимое является параметром программы или функции

Это, по-видимому, подтверждается руководством bash.

В строке log1 "${X[*]}" поэтому я ожидаю, что цитированное выражение будет расширено до "a | b", а затем будет передано функции log1. Функция имеет один строковый параметр, который он отображает. Почему происходит что-то еще?

Было бы здорово, если бы ваши ответы были поддержаны справочными/стандартными ссылками!

Ответ 1

IFS используется не только для объединения элементов ${X[*]}, но и для разделения неопределенного расширения [email protected]. Для log1 "${X[*]}" происходит следующее:

  • "${X[*]}", как и ожидалось, расширяется до a|b, поэтому $1 устанавливается на a|b внутри log1.
  • Когда [email protected] (без кавычек) разворачивается, результирующая строка a|b.
  • Некотированное расширение претерпевает разбиение слов на | как разделитель (из-за глобального значения IFS), так что echo получает два аргумента: a и b.

Ответ 2

Это потому, что $IFS установлен в |:

(X='a|b' ; IFS='|' ; echo $X)

Вывод:

a b

man bash говорит:

IFS Внутренний разделитель полей, который используется для разбиения слов после расширения...

Ответ 3

В разделе спецификации POSIX в разделе [Специальные параметры [(http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_02).

@

Расширяется до позиционных параметров, начиная с одного. Когда разложение происходит в двойных кавычках и где выполняется разделение поля (см. Раздел "Разделение поля" ), каждый позиционный параметр должен расширяться как отдельное поле с условием, что расширение первого параметра все равно должно быть связано с начальной частью оригинальное слово (предполагая, что расширенный параметр был встроен в слово), а расширение последнего параметра все равно должно быть соединено с последней частью исходного слова. Если нет позиционных параметров, расширение "@" должно генерировать нулевые поля, даже когда "@" имеет двойные кавычки.

*

Расширяется до позиционных параметров, начиная с одного. Когда расширение происходит в строке с двойными кавычками (см. Double-Quotes), оно должно расширяться до одного поля со значением каждого параметра, разделенного первым символом переменной IFS, или с помощью if, если IFS не задано. Если IFS установлено в пустую строку, это не эквивалентно ее отключению; его первый символ не существует, поэтому значения параметров конкатенируются.

Итак, начиная с цитированных вариантов (они проще):

Мы видим, что расширение * "расширяется [s] до одного поля со значением каждого параметра, разделенного первым символом переменной IFS". Вот почему вы получаете a|b от echo "${X[*]" и log2 "${X[*]}".

Мы также видим, что расширение @ расширяется так, что "каждый позиционный параметр расширяется как отдельное поле". Вот почему вы получаете a b от echo "${X[@]}" и log2 "${X[@]}".

Вы видели эту заметку о разделении полей в тексте спецификации? "где выполняется разбиение поля (см. раздел" Разделение поля ")? Это ключ к тайне здесь.

Вне кавычек поведение разложений одинаково. Разница в том, что происходит после этого. В частности, разбиение поля/слова.

Самый простой способ показать проблему - запустить код с включенным set -x.

Что вам нужно:

+ X=(a b)
+ IFS='|'
+ echo a b
a b
+ echo a b
a b
+ echo a b
a b
+ echo 'a|b'
a|b
+ echo ---
---
+ log1 a b
+ echo a b
a b
+ log1 a b
+ echo a b
a b
+ log1 a b
+ echo a b
a b
+ log1 'a|b'
+ echo a b
a b
+ echo ---
---
+ log2 a b
+ echo a b
a b
+ log2 a b
+ echo a b
a b
+ log2 a b
+ echo a b
a b
+ log2 'a|b'
+ echo 'a|b'
a|b

Следует отметить, что к моменту log1 вызывается во всех, кроме последнего, | уже уже.

Причина, по которой она уже исчезла, заключается в том, что без кавычек результаты разложения переменных (в данном случае расширения *) разделяются по слову/слову. А поскольку IFS используется как, чтобы объединить расширяемые поля, а затем снова разбить их, | получает проглатывание при разбиении поля.

И для завершения объяснения (для рассматриваемого случая) причина, по которой это не удается для log1, даже с цитированной версией расширения в вызове (т.е. log1 "${X[*]}", которая правильно расширена до log1 "a|b"), является потому что сам log1 не использует цитированное расширение @, поэтому разложение @ в функции само разбивается на слова (как это видно в echo a b в этом случае log1, а также все другие случаи log1).