我正试图明确地理解Bash解析器的业务顺序。
此wiki页面声明以下顺序:
- 读取行
- 处理/删除引号
- 以分号分隔
- 处理"特殊运算符",根据文章,它们是:
- 命令分组和大括号展开,例如
{…}
- 处理替换,例如
cmd1 <(cmd2)
- 重定向
- 管道
- 执行扩展,这些扩展未全部列出,但应包括:
- 支架扩展,例如
{1..3}
。出于某种原因,这篇文章把这个问题塞到了前一阶段- 颚化符展开,例如
~root
- 参数&可变展开,例如
${var##*/}
- 算术扩展,例如
$((1+12))
- 命令替换,例如
$(date)
- 分词,适用于展开结果的;使用CCD_ 8
- 路径名扩展或globbing,例如
ls ?d*
- 分词,适用于整行的;不使用
$IFS
- 执行
这不是引用,而是对链接文章内容的转述
此外,还有Bash手册页,这个SO的答案声称是基于这些页面。根据答案,命令解析的阶段如下:
- 初始分词
- 支架膨胀
- 波浪形展开
- 参数、变量和算术展开
- 命令替换
- 二次分词
- 路径扩展
- 报价删除
强调矿
我假设,作者所说的"初始单词分割"意味着对整行的分割,而"次要单词分割"则意味着对展开结果的分割。这将要求在命令解析过程中至少存在两个不同的标记化过程。
考虑到两个源之间的排序矛盾,相对于正在执行的其他操作,输入命令行被取消引号并拆分为单词/令牌的实际顺序是什么?
编辑注释:
为了解释部分答案,这个问题的早期版本有一个子问题:
为什么
cmd='var=foo';$cmd
产生bash: var=foo: command not found
?
shell解析的第一步是应用shell语法规则,这些规则必须提供POSIX shell命令语言语法规范中指定的语法的超集。
只有在这个初始阶段,才能检测到分配,而且只有在非常特殊的情况下:
ASSIGNMENT_WORD
令牌必须由解析器生成(请注意,解析器只运行一次,并且在进行任何扩展后不会重新运行!)=
字符本身及其前面的有效变量名不得加引号
如果不显式调用eval
(或将结果作为代码传递给另一个shell,或采取一些类似的显式操作),解析器永远不会在扩展结果上重新运行,因此,如果操作在进行扩展之前没有解析为赋值,则扩展的结果将永远不会生成赋值。
理解标记化和分词之间的区别很重要。标记化将输入划分为具有语法意义的标记,然后shell语法使用这些标记对输入进行语法分析。语法标记包括分号和圆括号(标准术语中的"运算符")。一种特殊类型的令牌是WORD。
正如标准所指出的,标记化基本上是解析输入的第一步(但如下文所述,它取决于引用字符的识别。)
WORD可以随后通过应用各种展开来进行解释。应用于每个单词的精确扩展集取决于语法上下文;并不是所有的单词都是一样的。这一点已记录在标准的叙述性文本中。应用于某些word的一种转换是分词,它根据字段分隔符的存在将一个word拆分为word列表,默认情况下为空白(可通过更改IFS
shell变量的值进行配置)。分词不会改变句法标记类型;事实上,当它发生时,句法分析已经完成。
并非所有单词都要进行分词。特别是,除非有一些扩展,并且只有扩展不在双引号内,否则不会执行分词。(即便如此,也不是在所有的句法语境中。)
将输入划分为令牌的算法必须与标准中的算法等效。该算法要求知道哪些字符被引用;大多数历史实现都是通过在内部用一个"引号"位标记每个输入字符来实现的。在标记化过程中是否删除引用字符在某种程度上取决于实现;该标准将引号删除步骤放在最后,但如果最终结果相同,则实现可以更早地完成。
请注意,=
不是运算符字符,因此它不会导致var=foo
被拆分为多个令牌。然而,以标识符开头、后跟=
的令牌由shell解析器进行特殊处理;它们稍后被视为参数分配。但是,如上所述,分词不会改变word的语法性质,因此shell解析器不会将分词产生的word视为参数赋值。
我同意,我的问题要求很高,我非常感谢所有宝贵的意见。我感谢@rici和@CharlesDuffy。
下面是Bash如何解释和执行代码的大致概述。
第1阶段:线路馈电
Shell根据行读取输入。
第二阶段:代币化
行被分割成标记——单词和运算符,由元字符分隔。尊重引用(、
'…'
、"…"
),替换别名,删除注释。令牌边界在内部记录。
元字符为:<space>
、<tab>
、<newline>
、|
、&
、;
、(
、)
、<
、>
。
第3阶段:命令解析
为管道、列表和复合命令(循环、条件、分组)解析行。这为Bash提供了执行子命令的顺序概念。然后,每个子命令由其自己的解析周期单独处理。
第四阶段:语法
指定(命令名称左侧的指定)和重定向将被删除并保存以备以后使用。
第5阶段:扩张
展开按顺序进行:
- 支架扩展,例如
{1..3}
- 颚化符展开,例如
~root
- 参数&可变展开,例如
${var##*/}
- 算术扩展,例如
$((1+12))
- 命令替换,例如
$(date)
- 过程替换,如果支持,例如
cat <(ls)
- 单词拆分,应用于展开的未引用结果,使用
IFS
变量作为分隔符 - 文件名扩展或globbing,例如
ls ?d*
- 报价删除:清除所有未报价的
、
‘
和"
,而不是由扩展产生的
第6阶段:重定向
重定向现在执行,然后删除。以前从管道进行的重定向可能会被覆盖。
如果该行不包含命令名,则重定向不会产生任何影响;否则它们仅影响所述命令。
第7阶段:作业
分配现在执行,然后删除。它们的值(=
右侧)经过:
- 波浪形展开
- 参数扩展
- 命令替换
- 算术扩展
- 报价删除
如果该行不包含命令名,则赋值会影响当前shell环境;否则,它们仅为所述命令而存在。
第8阶段:命令和论据
此时,如果没有生成命令名,则该命令将退出。
否则,该行的第一个单词将变为命令,后面的单词是arguments。
第9阶段:执行
现在,回答我的问题。
如上所述:
- 代币化发生在第2阶段;在第5阶段和第7阶段中发生分词。这两者是不同的概念
- 引号(和反斜杠)在第2阶段发挥作用,通常在第5阶段删除。对于任务,他们可以活到第7阶段
- 赋值在第4阶段被识别,因此它们不能来自第5阶段发生的变量扩展