Удивительное поведение расширения массива
Я был удивлен линией, отмеченной (!!)
в следующем примере:
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
).