Bash解析器在命令行中以什么顺序转义字符和拆分单词/令牌?

时间:2019-01-12 15:11:07

标签: bash parsing

我正在努力确定Bash解析器的业务顺序。


This Wiki页面要求以下顺序:

  
      
  1. 读取行。
  2.   
  3. 处理/删除报价。
  4.   
  5. 以分号分隔。
  6.   
  7. 处理“特殊运算符”,根据文章,它们是:      
        
    • 命令分组和大括号扩展,例如{…}
    •   
    • 流程替代,例如cmd1 <(cmd2)
    •   
    • 重定向。
    •   
    • 管道。
    •   
  8.   
  9. 执行扩展(未全部列出),但包括:      
        
    • 括号扩展,例如{1..3}。由于某种原因,本文将其纳入了上一阶段。
    •   
    • 波浪扩展,例如~root
    •   
    • 参数和变量扩展,例如${var##*/}
    •   
    • 算术扩展,例如$((1+12))
    •   
    • 命令替换,例如$(date)
    •   
    • 分词适用于扩展结果;使用$IFS
    •   
    • 路径名扩展或遍历,例如ls ?d*
    •   
  10.   
  11. 分词适用于整行使用$IFS
  12.   
  13. 执行。
  14.   

这不是引号,而是链接文章的措辞内容。


此外,还有Bash手册页,因此this声称基于这些页进行了回答。根据答案,命令解析的阶段如下:

  
      
  1. 初始分词
  2.   
  3. 扩军
  4.   
  5. 波浪线扩展
  6.   
  7. 参数,变量和算术扩展
  8.   
  9. 命令替换
  10.   
  11. 次要单词拆分
  12.   
  13. 路径扩展(aka遍历)
  14.   
  15. 删除报价
  16.   

重点增强

我假设,通过“初始单词拆分”,作者是指整行的拆分,而通过“第二单词拆分”,是指扩展结果的拆分。这将导致在命令解析期间至少存在两个不同的令牌化过程。


考虑到两个来源之间的顺序矛盾,相对于正在执行的其他操作,将输入命令行去引号并分成单词/标记的实际顺序是什么?


编辑说明:

为解释部分答案,该问题的早期版本有一个子问题:

  

为什么cmd='var=foo';$cmd会产生bash: var=foo: command not found

3 个答案:

答案 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解释和执行代码的粗略概述。

阶段1:换行

Shell按行读取输入。

第二阶段:令牌化

行被切成令牌-单词和运算符,由元字符分隔。尊重引用(\'…'"…"),别名被替换,注释被删除。令牌边界在内部记录。

元字符为:<space><tab><newline>|&;,{ {1}},()<

阶段3:命令解析

管道列表复合命令(循环,条件,分组)解析

Line。这使Bash知道了执行子命令的顺序。然后,每个子命令将通过其自己的解析周期进行单独处理。

阶段4:语法

分配(命令名称左侧的分配)和重定向已删除并保存以备后用。

第5阶段:扩展

按顺序执行扩展:

  1. 括号扩展,例如>
  2. 波浪扩展,例如{1..3}
  3. 参数和变量扩展,例如~root
  4. 算术扩展,例如${var##*/}
  5. 命令替换,例如$((1+12))
  6. 流程替换(如果支持),例如$(date)
  7. 分词,适用于扩展的未引用结果,使用cat <(ls)变量作为分隔符。
  8. 文件名扩展或遍历,例如IFS
  9. 删除行情:清除所有非引号ls ?d*\(不是由扩展引起的)。

阶段6:重定向

立即执行重定向,然后将其删除。来自管道的先前重定向可能会被覆盖。

如果该行不包含命令名,则重定向不起作用;否则它们只会影响所说的命令。

第7阶段:作业

现在执行分配,然后将其删除。它们的值(在"的右边)经历:

  • 波浪线扩展
  • 参数扩展
  • 命令替换,
  • 算术扩展
  • 去除报价。

如果该行不包含命令名,则分配会影响当前的shell环境;否则它们仅存在于上述命令中。

阶段8:命令和参数

这时,如果没有命令名称,命令将退出。

否则,该行的第一个单词将成为命令,随后的单词将成为参数。

第9阶段:执行


现在,回答我的问题。

上述内容如下:

  1. 令牌化发生在阶段2;分词发生在第5和第7阶段。这是两个不同的概念。
  2. 行情(和反斜杠)在第2阶段起作用,通常在第5阶段删除。对于作业,它们一直存在到第7阶段。
  3. 分配是在第4阶段识别的,因此它们不能来自第5阶段发生的变量扩展。