答案1
关键是 shell 在应用 Shell 语法规则(第 2.10 节)之前执行别名替换(第 2.3.1 节)。第一步是令牌识别(第 2.3 节):
2.3 令牌识别
令牌应从输入中的当前位置开始,直到根据以下规则之一分隔令牌为止;形成标记的字符与输入中的字符完全相同,包括任何引用字符。(...分隔标记的规则...)
一旦标记被定界,它就会按照语法的要求进行分类 外壳语法。
2.3.1 别名替换
在标记被定界之后,但在应用语法规则之前 外壳语法,应检查被识别为简单命令的命令名称单词的结果单词以确定它是否是未引用的, 有效的别名。
从粗体部分,我们得出结论,在到达别名查找步骤时,引用尚未被删除。 (用反斜杠转义也是引用。)
然后,发出\ls
orl\s
意味着 shell 将查找具有该确切文字名称的别名,即\ls
or l\s
。这样的别名甚至是不可能的,因此别名扩展不会发生,并且 shell 传递到语法规则,此时两者都解析为我们的老朋友ls
,因为“未加引号的 <backslash> 应保留后续字符的字面值”。如果 PATH shell 变量是普通变量,/bin/ls
则不带该选项执行-l
。
这也解释了,,,,,"ls"
...'ls'
跳过别名。l""s
l''s
答案2
文档
bash 手册做记录下来。我的强调:
每个简单命令的第一个字,如果不加引号,检查它是否有别名。
在 mksh 手册中:
当找到引用的单词时,别名扩展过程停止 (...)
在 zsh 手册中:
别名扩展是在除历史扩展之外的任何其他扩展之前对 shell 输入进行的。因此,如果为单词 foo 定义了别名,则可以通过引用该单词的一部分来避免别名扩展,例如
\foo
。任何形式的引用都可以,尽管也没有什么可以阻止为引用形式定义别名\foo
。
(什么也没有,除了setopt posix_aliases
。)
这比 dash 手册和 ksh 手册更好。破折号手册描述了保留字和别名之前的引用,但没有描述它们之间的交互。 ksh 手册指出保留字仅在未加引号时才被识别,但没有针对别名说明这一点。
基本原理
从历史上看,我认为这部分是作为语言设计选择而出现的,部分是为了简化实现。 Bourne shell 没有别名,但它确实具有仅在未引用时才识别保留字的行为。这是有道理的,因为解析器需要尽早知道一个词是否是保留词。特别是,将变量或命令替换的结果视为保留字会产生问题。考虑:
if condition
then
command1
"$else_or_not_else"
command2
fi
如果condition
是 false 并且 的值else_or_not_else
恰好是else
,应该command2
执行吗?这将需要解析器进行扩展$else_or_not_else
——但这部分代码被跳过,因为condition
是 false!如果“else”必须被识别为关键字,则 shell 解析器需要有一种处理引用但不处理替换的模式,这会增加另一层复杂性。
识别来自替换的别名也会有问题。别名可能类似于
alias a='command1; $command2'
别名中的;
是解析器需要考虑的命令分隔符。如果变量替换后发生别名扩展,则需要在变量替换后再次调用解析器。此外,在扩展别名时需要再次调用变量替换。因此,它简化了语言设计和解释器实现,以不识别来自替换的别名。再次强调,将引用和替换放在一起会更容易,这样既可以让用户理解语言,也可以让实现者创建解释器。
对保留字和别名保持相同的规则也是一个简化因素。