我正在努力确定Bash解析器的业务顺序。
This Wiki页面要求以下顺序:
- 读取行。
- 处理/删除报价。
- 以分号分隔。
- 处理“特殊运算符”,根据文章,它们是:
- 命令分组和大括号扩展,例如
{…}
。- 流程替代,例如
cmd1 <(cmd2)
。- 重定向。
- 管道。
- 执行扩展(未全部列出),但应包括:
- 括号扩展,例如
{1..3}
。由于某种原因,本文将其纳入了上一阶段。- 波浪扩展,例如
~root
。- 参数和变量扩展,例如
${var##*/}
。- 算术扩展,例如
$((1+12))
。- 命令替换,例如
$(date)
。- 分词,适用于扩展结果;使用
$IFS
。- 路径名扩展或遍历,例如
ls ?d*
。- 分词,适用于整行; 不使用
$IFS
。- 执行。
这不是引号,而是链接文章的措辞内容。
此外,还有Bash手册页,因此this声称基于这些页进行了回答。根据答案,命令解析的阶段如下:
- 初始分词
- 扩军
- 波浪线扩展
- 参数,变量和算术扩展
- 命令替换
- 次要单词拆分
- 路径扩展(aka遍历)
- 删除报价
重点增强
我假设,通过“初始单词拆分”,作者是指整行的拆分,而通过“第二单词拆分”,是指扩展结果的拆分。这将导致在命令解析期间至少存在两个不同的令牌化过程。
考虑到两个来源之间的顺序矛盾,相对于正在执行的其他操作,将输入命令行去引号并分成单词/标记的实际顺序是什么?
编辑说明:
为解释部分答案,该问题的早期版本有一个子问题:
为什么
cmd='var=foo';$cmd
会产生bash: var=foo: command not found
?
答案 0 :(得分:2)
Posix为外壳解释设置了precise procedure。但是,大多数shell(包括bash)都添加了自己的语法扩展名。此外,该标准并没有坚持要求它实际上是在使用算法;只是最终结果是相同的。因此,标准算法与有关单个外壳的描述之间存在一些差异。但是,大致轮廓是相同的。
了解令牌化和分词之间的区别非常重要。令牌化将输入分成语法上重要的令牌,然后,shell语法将其用于语法上分析输入。语法标记包括分号和括号之类的东西(标准术语中的“运算符”)。一种特殊的令牌是WORD。
如标准所述,令牌化基本上是解析输入的第一步(但是,如下所述,它取决于引号字符的标识。)
随后可以通过应用各种扩展来解释WORD。应用于每个单词的精确扩展集取决于语法上下文。并非所有单词都一样。在标准的叙述文字中有记录。应用于某些WORD的一种转换是单词拆分,默认情况下,该字段会根据字段分隔符的存在将一个WORD拆分为WORD列表(并且可以通过更改IFS
shell的值进行配置)变量)。分词不会更改语法标记类型;确实,到发生时,语法分析就完成了。
并非所有的单词都可以进行单词拆分。特别是,除非进行了某种扩展,否则只有在扩展不在双引号内时才进行单词拆分。 (即使那样,也并非在所有语法环境中都如此。)
将输入划分为令牌的算法必须与the standard中的算法相同。该算法要求知道哪些字符已被引用;大多数历史实现都是通过在内部用“带引号”标记每个输入字符来实现的。记号化期间是否删除引号字符在某种程度上取决于实现方式;该标准将引号删除步骤放在最后,但是如果最终结果相同,则实现可以更早地实现。
请注意,=
不是运算符,因此不会导致var=foo
被拆分为多个标记。但是,shell解析器会特别处理以标识符开头的=
后缀的令牌。它们后来被视为参数分配。但是,如上所述,分词不会改变WORD的句法性质,因此Shell解析器不会将分词产生的WORD看起来像参数分配一样。
答案 1 :(得分:2)
shell解析中的非常第一步正在应用shell语法规则,这些规则必须提供the POSIX shell command language grammar specification中指定的语法的超集。
仅在此初始阶段才能检测到分配,并且仅在非常特殊的情况下:
ASSIGNMENT_WORD
令牌必须由解析器生成(请注意,解析器仅运行一次 ,并且在进行任何扩展后都不会重新运行!)=
字符本身及其前面的有效变量名称必须不加引号。在没有显式调用eval
(或将结果作为代码传递到另一个shell或采取一些类似的显式操作)的情况下,解析器永远不会在扩展结果上重新运行,因此扩展的结果将如果操作未在扩展之前解析为分配,则从不生成分配。
答案 2 :(得分:0)
我同意,我的问题要求很多,我非常感谢所有宝贵的意见。我感谢@rici和@CharlesDuffy。
下面是Bash解释和执行代码的粗略概述。
Shell按行读取输入。
行被切成令牌-单词和运算符,由元字符分隔。尊重引用(\
,'…'
,"…"
),别名被替换,注释被删除。令牌边界在内部记录。
元字符为:<space>
,<tab>
,<newline>
,|
,&
,;
,{ {1}},(
,)
,<
。
Line。这使Bash知道了执行子命令的顺序。然后,每个子命令将通过其自己的解析周期进行单独处理。
分配(命令名称左侧的分配)和重定向已删除并保存以备后用。
按顺序执行扩展:
>
。{1..3}
。~root
。${var##*/}
。$((1+12))
。$(date)
。cat <(ls)
变量作为分隔符。IFS
。ls ?d*
,\
和‘
(不是由扩展引起的)。立即执行重定向,然后将其删除。来自管道的先前重定向可能会被覆盖。
如果该行不包含命令名,则重定向不起作用;否则它们只会影响所说的命令。
现在执行分配,然后将其删除。它们的值(在"
的右边)经历:
如果该行不包含命令名,则分配会影响当前的shell环境;否则它们仅存在于上述命令中。
这时,如果没有命令名称,命令将退出。
否则,该行的第一个单词将成为命令,随后的单词将成为参数。
现在,回答我的问题。
上述内容如下: