(确切地说)什么是“列表上下文”(和“字符串上下文”)?

(确切地说)什么是“列表上下文”(和“字符串上下文”)?

我已经多次看到“列表上下文”和“字符串上下文”的使用。

我知道并理解 Perl 中此类描述的用法。他们适用于$和@

然而,当在 shell 描述中使用时:

它们似乎是一个分散的术语,在任何地方都没有定义,或者
充其量也没有很好的记录。

POSIX 中没有对此的定义,根据谷歌

这是 (由此)它的要点是什么? :

简而言之,只要需要单词列表或模式,就需要双引号。在解析器需要原始字符串的上下文中,它们是可选的。

但这似乎是一个难以使用的术语。当“需要结果”时,我们如何才能找到“结果应该是什么”才能知道它是字符串还是列表上下文。

或者它可以被精确且正确地定义吗?

答案1

标准shell语言中没有这样的概念。没有“上下文”,只有扩展步骤。

引号首先在生成单词的标记化中被识别。他们将单词粘在一起,形成abc"spaces here"xyz一个“单词”。

需要理解的重要一点是,引号在后续扩展步骤中得到保留,并且原始引号与扩展中可能出现的引号区分开来。

参数的扩展不考虑双引号。不过,稍后会发生字段拆分过程,这又回到了第一次标记化。引号再次防止分裂,并且再次被保留。

路径名扩展(“globbing”)发生在此分割之后。保留的引号可以防止这种情况发生:在引号内无法识别通配符运算符。

最后,引号在称为“引号删除”的后期阶段被删除。当然,只有原来的引号!

POSIX 做得很好介绍过程以一种可以理解的方式;试图用无关的概念(可能会产生误导)来揭开它的神秘面纱只会混淆理解。

人们乱扔乱扔特别指定像“列表上下文”这样的概念应该将他们的想法形式化,以至于它可以为所有处理提供完整的替代规范,这是等效的(产生相同的结果)。然后,避免在并行设计之间混合概念:使用一种解释或另一种解释。 “列表上下文”或“字符串上下文”在 shell 扩展理论中是有意义的,其中这些概念得到了很好的定义,并且处理步骤是围绕这些概念组织的。

如果我猜的话,“列表上下文”指的是 shell 正在处理标记化单词列表(例如两个单词的列表)的想法{foo} {abc" x "def}。引号不是第二个单词的一部分:它的内容实际上是abc x def;它们是语义引号,可以防止空格分割。在这些引号内,我们有“字符串上下文”。

然而,这些扩展步骤的可能实现并不是实际上有被标识为原始引号的引号,但是某种列表数据结构,也就是说{foo} {abc" x "def},一个列表的列表,其中被引用的部分被标识为不同类型的节点(并且引号消失了)。使用 Lisp 表示法可能是:

(("foo") ;; one-element word
 ("abc" (:dq-str " x ") "def")) ;; three-element word

没有标签的节点是文字文本,:dq-str是双引号区域。另一种类型可以:sq-str用于单个引用的项目。

扩展可以遍历这个结构,然后根据它是否正在查看字符串对象、表达式:dq-str或其他内容来执行不同的操作。文件扩展和字段分割将在:dq-str或中被抑制:sq-str。但参数扩展发生在 内:dq-str。然后,“引用删除”将对应于最终传递,该传递获取片段并连接字符串,展平内部列表结构并丢失类型指示符号,从而导致:

("foo"
 "abc x def") ;; plain string list, usable as command arguments

现在,请注意我们在第二项中的("abc" (:dq-str " x ") "def").第一个和最后一个项目是展开的:它们是列表的直接元素,因此我们可以说它们位于“列表上下文”中。而中间的内容" x "被包裹在:dq-str表达式中,即“(双引号)字符串上下文”。

“列表上下文”中的“列表”指的是什么,如果没有像这样明确定义的模型,任何人都可以猜测。是主单词表吗?或者代表一个单词的块列表?

答案2

由于您引用的大多数事件都是我所发生的,我觉得我必须在这里给出一个答案,尽管我主要会解释@Gilles。

我一直在使用列出上下文标量/非列表上下文(比字符串上下文至少自 2004 年以来,如果不将其理解为非列表上下文,可能会造成数十次混乱新闻组网或者UNIXSE大部分时间都在讨论在类似 Bourne 的 shell 中不引用扩展的影响的文章中。我不记得之前有人要求澄清我的意思(我确实这样做过)经常尝试举一些此类背景的例子来进行说明

我没有在任何 shell 语言的正式规范中使用它,这只是帮助向其他人解释 shell 行为的英文文本。

那不是官方术语尽管这显然是受到perl官方(文档中)术语的启发。我无法判断在我之前是否有其他人在 Unix shell 环境中使用过这些(尽管他们很可能使用过),但从那以后肯定有人使用过。我不声称拥有它。

列出上下文(至少当我在使用它的上下文中使用它时)只是意味着 shell 期望任意数量的元素的上下文。尽管标量/非列表/字符串上下文将是只需要一个(或者如果需要的话,一个字符串/标量)的地方,就像在perl.在大多数类似 Bourne 的 shell 中,这些列表上下文是:

  • 简单的命令参数(如)echo elements
  • for i in elements
  • array=(elements)(以及带有 的变体+=

有些 shell 有更多类似的内容:

  • cmd < elements其中zsh执行类似于(如, but )的操作。cat -- elements | cmdnl < *.txtnl < {foo,bar}.txtnl < foo.txt < bar.txt
  • cmd > elements(以及带有>| >>...的变体)zsh其中执行类似的操作cmd | tee -- elements
  • elements() { code; }zsh一次定义一个或多个函数(或者如果元素解析为一个空列表(尽管文字() { echo x; }是一个匿名函数))。
  • compound=(foo=(elements) elements)或中的等等。matrix=((elements) (elements))ksh93
  • ETC。

在这些情况下,通常情况下,glob 会被扩展,如果您不想对它们应用split+glob (或者只是删除空,zsh除非您启用shwordsplit/ sh 兼容性选项) ,则需要引用您的扩展。globsubst

例如,如果您替换元素*.txt上面的示例中,*.txt将扩展到当前目录中的 txt 文件列表。

如果您正在寻找 POSIX 规范中的等效项,请查找展开 glob 的上下文。 POSIX,至少在一个实例中将其称为将执行字段分割的上下文(实际上是在之后更改的措辞我向奥斯汀小组提出了之前措辞的问题)。当然,这个措辞对于回答以下问题不是很有用:执行字段分割的位置

标量上下文将是其他上下文。

scalar=*.txt
case *.txt in...
[[ -f *.txt ]]

*.txt无法扩展,因为 shell 只期望细绳。

作为警告/限制,这些术语不能清楚地捕获 shell 中发生的情况,例如/ cmd > *(不在POSIX 模式下时)、(使用而不是语法)或(仅在与某些交互时),在这些地方可以看到作为另一个cmd > ~(N)patterna=(); b=; c=(a b); d=*; IFS=:; e=a:b; cmd 1> "${a[@]}" 2> $b 3> "${c[@]}" 4> $d 5> $ebashyashksh88set -Avar=(...)ksh93列出上下文除了只需要一个包含一个元素的列表(对于某些情况,拆分和通配符的工作方式有所不同)。

答案3

措辞“列表上下文”和“字符串上下文”来自 Perl,但类似的概念也适用于 shell 语言。请注意,这些是相似的概念:上下文的种类和上下文类型的后果是不同的。

这个单词语境是编程语言语义中的一个技术术语。它是精确的含义与特定的语义形式化相关,这超出了本答案的范围。这认知的含义是代码片段周围环境的性质。例如,说代码片段$foo在不同的上下文中具有不同的含义意味着包含的程序的行为$foo取决于$foo程序中出现的内容的性质。

shell 的语义相当复杂。它并不完全属于编程语言入门教科书中的传统类别。 shell 程序的执行可以分为两个阶段(请注意,这是一种呈现语义的方式,并不意味着 shell 解释器必须以这种方式分解):

  1. 解析阶段将字符串(源文件的内容或 的参数的内容-c)转换为抽象语法树。在里面POSIX规范,这对应于步骤 2 和 3(标记识别和解析)。 POSIX 规范定义语法规则描述树的形状。请注意,这不是上下文无关语法 - 演示基于上下文无关语法的通常演示,但注释“应用规则” 使其成为一个更复杂的数学对象。

  2. 执行阶段对树的节点执行一些评估,并调用外部命令。在 POSIX 规范中,这对应于步骤 4-7(扩展、重定向、命令执行和等待)。

扩张是一个应用于抽象语法树中特定类型节点的过程,POSIX 称之为WORD“单词”。它可以分为两组。

  1. 在 POSIX 术语中,第一组扩展包括波形符扩展(例如~foo/home/foo),参数扩展(例如$foo→如果的bar值为),foobar命令替换(例如$(foo)bar如果命令的输出foobar)并且算术展开(例如$((2+2))4)。第一组扩展对每个单词执行,不包括由于单引号内或前面有反斜杠而“引用”的字符。这组扩展的输出大约是一个带有注释的字符串(我将在下面解释近似值)。

  2. 第二组扩展包括场分裂路径名扩展(通常称为“文件名生成”或“通配符”)。这组扩展将带注释的字符串转换为字符串列表。这组扩展是在执行第一组的位置的子集上执行的:它不会在用双引号引起来的单词部分上执行,并且如果单词位于抽象语法树中的某些位置,则根本不执行。这就是列表和字符串上下文的用武之地:在某些上下文中,即对于抽象语法树中的某些位置类别,执行第二组扩展。这些都是列出上下文,如此命名是因为扩展过程的结果是一个(字符串)列表。在其他情况下,称为字符串上下文,不执行第二组扩展,并且扩展过程的结果是单个字符串。

POSIX 描述报价删除就像最后一个扩展阶段一样。这是解释引用的一种方式,字段分割之前的所有扩展都被定义为从字符串到字符串的转换。例如,给定单词'$foo'$bar\$qux,假设变量的值为barvalue参数扩展会将其转换为'$foo'value\$qux,而其他第一组扩展使字符串保持不变。引号删除最后删除引号以获得$foovalue$qux

删除报价的演示需要在每个阶段执行报价匹配。更容易遵循和实现并给出相同最终结果的演示是执行产生部件列表的取消引用阶段。每个部分都有注释以记住它是否被引用。例如,'$foo'$bar\$qux对以下部分进行反引号:引用$foo、未引用的变量扩展bar、引用$、裸露q、裸露u、裸露x。 (区分“引用”和“裸”对于识别分配和决定是否扩展别名等事情是必要的。)第二阶段扩展仅发生在列表上下文中未引用的部分。

POSIX 通过显式列出扩展阶段来指定是否发生第二组扩展。例如,“在分配值之前,每个变量分配都应进行波浪号扩展、参数扩展、命令替换、算术扩展和引号删除”。一种更简单的表达方式是,仅发生第一组扩展,即分配是字符串上下文。只有两个上下文,因为只有两组用于执行扩展的规则:要么执行第一组的所有规则(字符串上下文),要么按顺序执行两组(列表上下文)。

(实际上,为了完整起见,还有第三种上下文:case模式上下文。在case模式中,仅执行第一组扩展(就像在字符串上下文中一样),但第二组扩展的一部分是相关的 - 不带引号的通配符字符是字符串匹配的通配符。)

语言的定义指定哪些上下文是列表上下文,哪些是字符串上下文。原则上,这可以是任意的。然而,其背后有一个直觉:在语法需要令牌列表的地方WORD,对这些令牌执行第二组扩展,而在语法需要单个 的地方WORD,不执行第二组扩展。解释这一点的一个简单方法是,如果语法需要列表,则它是列表上下文;如果语法需要单个字符串,则它是字符串上下文。

相关内容