声明命令和外壳扩展

Axe*_*ton 5 bash declare

我偶然发现了以下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")
Run Code Online (Sandbox Code Playgroud)

由于壳膨胀

  1. 支撑扩展
    • 波浪号扩展
    • 参数和变量扩展
    • 算术展开
    • 过程替换
    • 命令替换
  2. 分词
  3. 文件名扩展

在将其拆分为标记(然后是去除引号)之后在命令行上执行,但在执行最终命令之前,我不希望第 9 行失败。

它背后的基本原理是什么,这使得bash不接受第 9 行?或者,换种说法,我在第 9 行的处理方式中遗漏了什么bash使其失败但使第 10 行成功?

在任何情况下,引用并不总是直接起作用,如果数组元素是带有空格的字符串,则需要额外注意。

mos*_*svy 7

tl;博士; 我认为这只是一个语法怪癖,你不应该假设它背后有一些宏伟的设计。

Bash 正在使用 bison/yacc 生成的解析器,但就像许多其他语言(C、perl 等)一样,它不是一个“干净”的解析器,但它也保持一些状态parser_state变量中的语法分开/平行。

保存在该状态变量中的标志是PST_ASSIGNOK。当一些被解析为WORD标记的内置函数ASSIGNMENT_BUILTIN在其标志中时,这将被设置。

这种“分配建宏”是localtypesetdeclarealiasexportreadonly

在此类内置函数的右侧赋值之后使用时,PST_ASSIGNOK将指示解析器将括号视为WORD标记的一部分。但它不会更改确定当前标记是否实际上是赋值的规则:由于${foo}=(...)不是可接受的赋值,因此不会将其解析为单个单词,并且括号将触发语法错误,就像 in 一样echo foo(bar)

解析命令行后,它将被扩展,作为扩展的一部分,任何复合赋值(WORD标记为W_COMPASSIGN) likevar=(1 2)都将执行并替换为var,然后将其作为参数传递给内置函数 like declare。但是,如果declare在所有扩展之后,得到形式为 的参数var=(...),它将再次解析并扩展它自己。

所以,varname=foo; declare "$var=(1 2 3)"可能类似于declare foo='(1 2 3)'. 或 to 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")
Run Code Online (Sandbox Code Playgroud)

我认为依赖这种极端情况不是一个好主意:

$ 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 `('
Run Code Online (Sandbox Code Playgroud)