我对分词在 中扮演的角色感到困惑zsh
。我在使用 C、Python 或 MATLAB 编程时没有接触过这个概念,这引发了我对为什么分词似乎是 shell 编程特有的东西的兴趣。
我之前在这个网站和其他网站上读过有关分词的内容,但还没有找到这个概念的明确解释。维基百科有一个分词的定义但似乎没有关于它如何应用于 Unix shell 的参考资料。
这是我的困惑的一个例子zsh
:
在里面Z 壳常见问题解答,我读了以下内容:
3.1: 为什么
$var
wherevar="foo bar"
没有达到我的预期?在大多数 Bourne-shell 衍生产品中,多字变量(例如)
var="foo bar"
在传递给命令或在循环中使用时被拆分为单词for foo in $var
。默认情况下,zsh 没有这种行为:变量保持不变。 (这不是错误!请参见下文。)该选项的SH_WORD_SPLIT
存在是为了提供兼容性。
然而,在 Z Shell 手册中,我读了以下内容:
SH_WORD_SPLIT (-y) <K> <S>
导致对未加引号的参数扩展执行字段拆分。注意这个选项无事可做 与分词。 (请参阅参数扩展。)
为什么说SH_WORD_SPLIT
有没事做与分词?这不就是分词吗?
答案1
早期的 shell 只有一种数据类型:字符串。但操作字符串列表是很常见的,通常是将多个文件名作为参数传递给程序时。拆分的另一个常见用例是命令输出结果列表时:命令的输出是字符串,但所需的数据是字符串列表。要将文件名列表存储在变量中,可以在它们之间添加空格。然后是这样的shell脚本
files="foo bar qux"
myprogram $files
myprogram
当 shell 将字符串拆分$files
为单词时,使用三个参数进行调用。当时,文件名中的空格要么被禁止,要么被广泛认为尚未完成。
这科恩壳引入数组:您可以将字符串列表存储在变量中。 Korn shell 仍然与当时建立的 Bourne shell 兼容,因此裸变量扩展不断进行分词,并且使用数组需要一些语法开销。你可以写上面的片段
files=(foo bar qux)
myprogram "${files[@]}"
Zsh 从一开始就有数组,它的作者选择了更合理的语言设计,但牺牲了向后兼容性。在 zsh 中(默认扩展规则下)$var
不执行分词;如果你想在变量中存储单词列表,你应该使用数组;如果你真的想要分词,你可以写$=var
.
files=(foo bar qux)
myprogram $files
如今,文件名中的空格是您需要处理的问题,一方面是因为许多用户希望它们能够工作,另一方面因为许多脚本是在安全敏感的上下文中执行的,而攻击者可能会控制文件名。所以自动分词通常很麻烦;因此,我的一般建议是始终使用双引号,即 write "$foo"
,除非您了解为什么在特定用例中需要分词。 (请注意,裸变量扩展也会经历通配符。)
在我的回答中,我使用了“分词”一词。这也称为“字段分割”,因为可以通过设置变量来配置单词(也称为字段)的构成IFS
:其中的任何字符IFS
都被视为单词分隔符,单词是不是单词分隔符的字符序列。默认情况下,IFS
包含基本空白字符(ASCII 空格、制表符和换行符 — 不是回车符、不可分割的空格等)。 zsh 手册使用“分词”仅指解析 shell 代码的步骤,这与字段/词拆分无关,后者是变量和命令替换后发生的扩展的一部分。
答案2
在 Zsh 的这种特定情况下,字拆分的定义与字段拆分略有不同。
考虑一下prog a b c
,无论您如何设置,它都会传入三个参数IFS
。这是单词分裂。
如果这样做A="a b c"; prog $A
,它将传递三个参数(如果IFS
包含空格)或一个参数,否则传递。这是场地分裂。
这里的定义很微妙。 Zsh 文档试图说明的是,即使您禁用该选项,prog a b c
仍然会得到单独的参数(这是人们总是期望的)。
答案3
分词实际上并不是特定于 shell 的。
大多数需要解析文本输入的程序都会使用某种形式的分词作为第一步。它是在从这些“单词”中识别数字、运算符、字符串、标记以及它们需要处理的任何类似实体之前完成的。
shell 的具体之处在于它们必须正确构建名为(C argc/argv、python sys.argv)的命令的参数列表,包括传递带有嵌入空格、空参数、自定义分隔符等的参数。许多 shell 使用 IFS 变量来提供一定的灵活性。