Я случайно наткнулся на следующее bash
поведение, которое для меня оказалось неожиданным.
# The following works
$ declare bar=Hello # Line 1
$ declare -p bar # Line 2
declare -- bar="Hello"
$ foo=bar # Line 3
$ declare ${foo}=Bye # Line 4
$ declare -p bar # Line 5
declare -- bar="Bye"
# The following fails, though
$ declare -a array=( A B C ) # Line 6
$ declare -p array # Line 7
declare -a array=([0]="A" [1]="B" [2]="C")
$ foo=array # Line 8
$ declare -a ${foo}=([0]="A" [1]="XXX" [2]="C") # Line 9
bash: syntax error near unexpected token `('`
# Quoting the assignment fixes the problem
$ declare -a "${foo}=(A YYY C)" # Line 10
$ declare -p array # Line 11
declare -a array=([0]="A" [1]="YYY" [2]="C")
С момента расширения оболочки
- Расширение скобок
- Расширение тильды
- Расширение параметров и переменных
- Арифметическое расширение
- Процесс замены
- Замена команды
- Разделение слов
- Расширение имени файла
выполняется в командной строке после ее разделения на токены (с последующим удалением кавычек), но до выполнения последней команды я бы не ожидал, что строка 9 завершится ошибкой.
В чем причина, по которой bash
не принимается строка 9?Или, говоря по-другому,Что я упускаю из виду в способе обработки строки 9, из bash
-за чего она терпит неудачу, но строка 10 выполняется успешно?
В любом случае, кавычки не всегда работают напрямую и потребуют особого внимания, если элементы массива представляют собой строки, например, с пробелами.
решение1
tl;dr; Я думаю, что это просто синтаксическая особенность, и не стоит предполагать за ней какой-то грандиозный замысел.
Bash использует сгенерированный bison/yaccпарсер, но, как и во многих других языках (C, Perl и т. д.), это не «чистый» парсер, но он также сохраняет некоторыесостояниеотдельно/параллельно грамматике в parser_state
переменной.
Флаг, хранящийся в этой переменной состояния, — это PST_ASSIGNOK
. Он будет установлен, когда некоторая встроенная функция, которая была проанализирована как WORD
токен, имела ASSIGNMENT_BUILTIN
в своих флагах.
Такими «встроенными функциями присваивания» являются local
, typeset
, declare
, alias
, export
и readonly
.
Это PST_ASSIGNOK
заставит парсер считать скобки частью токена WORD
при использовании после назначения справа от такого встроенного. Но это НЕ изменит правила, которые определяют, является ли текущий токен на самом деленазначение: Поскольку ${foo}=(...)
это недопустимое назначение, оно не будет проанализировано как одно слово, а скобки вызовут синтаксическую ошибку, как в echo foo(bar)
.
После того, как командная строка будет проанализирована, она будетрасширенный, и как часть расширений, любое составное присваивание ( WORD
которое было отмечено как W_COMPASSIGN
) как var=(1 2)
будет выполнено и заменено на var
, которое затем будет передано как аргумент встроенному как declare
. Но если declare
, после всех расширений, получает аргумент формы var=(...)
, он сам снова его проанализирует и расширит.
Итак, varname=foo; declare "$var=(1 2 3)"
может быть похоже на declare foo='(1 2 3)'
. Или на declare foo=(1 2 3)
, в зависимости от того, была ли уже определена переменная:
$ declare 'foo=(1 2 3)'; typeset -p foo
declare -- foo="(1 2 3)"
$ declare foo=(1); typeset -p foo
declare -a foo=([0]="1")
$ declare 'foo=(1 2 3)'; typeset -p foo
declare -a foo=([0]="1" [1]="2" [2]="3")
Я не думаю, что стоит полагаться на этот частный случай:
$ declare 'bar=(1 ( )'; typeset -p bar
declare -- bar="(1 ( )"
$ declare bar=(1); typeset -p bar
declare -a bar=([0]="1")
$ declare 'bar=(1 ( )'; typeset -p bar
bash: syntax error near unexpected token `('