- CLI
- 开发工具
【免费下载链接】fish-shell
The user-friendly command line shell.
string unescape是 fish-shell 内置字符串处理命令族(stringbuiltin)中负责展开(unescape)转义序列的子命令,功能上与string escape恰好互逆:escape把任意字符串转换为可安全用于eval、变量名、URL 或正则的形态,unescape则把这些编码后的形态还原为原始字符串。本文以 string-unescape.rst 为骨架,结合其上游说明文档 string-escape.rst 与仓库中crates/common的底层实现,完整讲解语法、--style各取值、失效输入的处理规则、源码级解码原理及测试验证方法。读完本文,你将能熟练使用string unescape完成「编码→解码」的往返操作,并理解其在 fish 内部词法解析中的角色。
功能定位:string escape的逆操作
string unescape与string escape配对使用。先看escape的语法(见 string-escape.rst):
string escape [-n | --no-quoted] [--style=] [STRING ...] string unescape [--style=] [STRING ...]escape按多种方式将每个STRING转义:
--style=script(默认):修改字符串使其可以回传给eval并重新产生原始参数。默认会转义所有特殊字符,并在可能时用引号简化输出;若指定-n/--no-quoted,则不使用简化引号格式。退出状态:至少成功转义一个字符串返回 0,否则返回 1。--style=var:通过十六进制编码所有非字母数字字符,保证字符串可作变量名使用。编码前先把字符串转换为 UTF-8。--style=url:通过十六进制编码 URL 中不合法的字符,保证字符串可作 URL 使用。编码前同样先转 UTF-8。--style=regex:为在正则表达式中进行字面匹配而转义输入字符串,编码前先转 UTF-8。
而string unescape正是执行上述过程的逆向操作。文档明确给出了其不变量:
If the string to be unescaped is not properly formatted it is ignored. For example, doing
string unescape --style=var (string escape --style=var $str)will return the original string. There is no support for unescaping--style=regex.
即:格式不正确的输入会被直接忽略;escape --style=var与unescape --style=var组合必然还原原串;regex 风格没有对应的 unescape 实现——因为正则转义(如\.)在语义上无法被无歧义地还原。
语法与参数说明
string unescape的完整形式:
string unescape [--style=] [STRING ...]--style=:可选,取值script(默认)、var、url。传入其他值会报错(详见下文「失效输入与错误处理」)。STRING ...:零个或多个待解码的字符串;不提供任何字符串时同样按失败处理。
从源码 unescape.rs 可以看到参数解析的细节:
const LONG_OPTIONS: &'static [WOption<'static>] = &[ // FIXME: this flag means nothing, but was present in the C++ code // should be removed wopt(L!("no-quoted"), NoArgument, 'n'), wopt(L!("style"), RequiredArgument, NON_OPTION_CHAR), ]; const SHORT_OPTIONS: &'static wstr = L!("n");注意两个值得说明的实现事实:
- 兼容性的
-n/--no-quoted选项仍然被解析(parse_opt中'n' => self.no_quoted = true),但源码注释明确标注这是一个从 C++ 时代遗留下来的、已无实际意义的选项("FIXME: this flag means nothing"),解码流程不会使用它。也就是说string unescape -n不会报错,但行为与不带该选项完全一致。 --style是必带参数的选项(RequiredArgument),解析后通过TryFrom<&wstr>将字符串转换为内部枚举。
三种解码风格:script / var / url
unescape的命令分发逻辑在 crates/common/src/lib.rs 的unescape_string()函数中:
pub fn unescape_string(input: &wstr, style: UnescapeStringStyle) -> Option<WString> { match style { UnescapeStringStyle::Script(flags) => unescape_string_internal(input, flags), UnescapeStringStyle::Url => unescape_string_url(input), UnescapeStringStyle::Var => unescape_string_var(input), } }对应枚举定义:
pub enum UnescapeStringStyle { Script(UnescapeFlags), Url, Var, }其中script风格携带一组UnescapeFlags(special、incomplete、no_backslashes),用于控制词法级解码行为;url与var则是纯字节级十六进制解码。
script:词法级反解,最贴近 shell 语义
--style=script(默认)走unescape_string_internal(),它本质上是 fish 词法分析(tokenizer)对「未加引号单词」的逆向:逐字符扫描输入,处理反斜杠转义、单引号、双引号,并把 shell 特殊字符还原为内部表示:
\开头进入unescape_one()处理反斜杠转义序列,例如\n、\x07等;- 未加引号状态下遇到
~(且位于单词起始位置)会还原为家目录符号; %self被识别并还原为进程自引用标记;*还原为通配符内部标记(连续**合并为递归通配符);$还原为变量展开标记($(形式的命令替换除外);{、}还原为花括号展开标记,并记录花括号配对位置。
也就是说script风格解码后得到的字符串带有 fish 内部解释层使用的标记字符,它还原的并非「直接可显示的普通文本」,而是「与原转义字符串等价的、待解释的 token 内容」。这正是它适合配合eval往返使用的原因。
url:%XX百分号解码
unescape_string_url()实现如下(见 crates/common/src/lib.rs):
- 输入中遇到
%时,读取其后的两个十六进制字符并合成一个字节;遇到%%则还原为单个%; - 若
%后缺少合法十六进制位(如%位于字符串末尾),解码失败返回None; - 任何大于
\u{7F}的字符(即非 ASCII)都会导致返回None,因为 URL 风格的转义产物按定义应只含 ASCII 字符。
由此,escape --style=url产生的%C3%B6(UTF-8 编码的ö)能被正确还原为ö;而手写的不规范%序列则会被整体忽略。
var:_前缀十六进制解码
unescape_string_var()实现见 crates/common/src/lib.rs,规则与url风格对称:
_后跟两位合法大写十六进制字符时,解码为一个字节(例如_C3_B6_→ö);__还原为单个下划线_;_后既不是下划线也不是合法十六进制字符时,该_被保留(它只是编码产物中用于提升可读性的分隔符);- 若
_出现在字符串末尾且此前发生过十六进制编码,则正常结束解码;否则视为意外结尾返回None; - 与非 ASCII 输入同理,解码产物应为纯 ASCII 输入,非法字符直接失败。
这套规则与escape --style=var的编码规则(字母数字保留、其余字节转为_XX_形式)精确对称,从而保证string unescape --style=var (string escape --style=var $str)的无损往返。
实战示例与往返验证
文档给出的基础示例(见 string-escape.rst):
>_ echo \x07 | string escape \cg\x07(BEL 控制字符)被转义为可见的\cg。反向操作即可取回原字符:
>_ echo \x07 | string escape | string unescape # 输出 BEL 控制字符(不可见)仓库测试 tests/checks/string.fish 提供了成体系的往返用例,可直接复现:
# 多字节字符的 url / var 往返 string escape --style=url aöb | string unescape --style=url # CHECK: aöb string escape --style=url 中 | string unescape --style=url # CHECK: 中 string escape --style=var aöb | string unescape --style=var # CHECK: aöb string escape --style=var 中 | string unescape --style=var # CHECK: 中 # 含引号、井号、通配符、反斜杠等特殊字符的 script 往返 string unescape --style=script (string escape --style=script 'a b#c"\'d') # CHECK: a b#c"'d # 含换行的 url 往返 string unescape --style=url (string escape --style=url \na\nb%c~d\n) # CHECK: a # CHECK: b%c~d # 含下划线与换行的 var 往返 string unescape --style=var (string escape --style=var a\nghi_) # CHECK: a # CHECK: ghi_ # 纯字母数字、下划线、连字符的 var 往返 string unescape --style=var (string escape --style=var abc) # CHECK: abc string unescape --style=var (string escape --style=var _a_b_c_) # CHECK: _a_b_c_ string unescape --style=var -- (string escape --style=var -- -) # CHECK: -把这些用例串成脚本可以直观验证「编码→解码」的幂等性:escape --style=var会把a b#c"'d变成带_20_、_23_等编码的形式,而unescape --style=var又将其完整还原。
失效输入与错误处理
unescape对无法正确解码的输入采取「静默忽略、整体失败」的策略,这一点在文档与源码中均有体现:
- 单条输入解码失败被忽略:
unescape_string()对每个输入返回Option,返回None表示格式非法。命令主循环(见 unescape.rs 的handle)只会在unescape_string返回Some时才把结果写入输出流并计数。 - 退出状态由成功条数决定:只要至少解码成功一个字符串,命令返回 0;一个都未成功(包括未提供参数、所有输入均非法)则返回
STATUS_CMD_ERROR(即状态 1)。这一行为与escape的退出约定对称。 - 未知 style 直接报错:
TryFrom<&wstr>(见 crates/common/src/lib.rs)只接受script、var、url,其他取值返回Err(L!("Invalid escape style"))。测试覆盖了这一路径:
string unescape --style=unknown-style # CHECKERR: string unescape: Invalid style value 'unknown-style'- 不支持 regex 的解码:
--style=regex的escape产物(如\.ext、\^)在unescape中没有对应风格,传入--style=regex会得到Invalid style value错误。这是有意设计:正则转义失去原上下文后不可无歧义还原。
在 string 命令族中的位置与底层调用链
string unescape是 fish 内置string命令的子命令分发入口之一。在 src/builtins/string.rs 中注册:
mod unescape; // ... "unescape" => unescape::Unescape::default().run(parser, streams, args),调用链可归纳为:
- 用户输入
string unescape --style=... STRING ...; Unescape::handle()通过arguments()迭代器逐个读取参数;- 每个参数调用
fish_common::unescape_string(&arg, style); - 底层按风格分发到
unescape_string_internal/unescape_string_url/unescape_string_var; - 成功的结果追加到标准输出(逐行追加换行符),并累计成功计数;
- 依据成功计数决定退出状态 0 或 1。
值得一提的是,unescape_string_internal并非仅供命令使用:它与 fish 补全机制共享底层词法逻辑(源码注释中提到补全机制会传入不完整的 token 片段,花括号配对此时不作硬性断言),是 fish 把「外部编码字符串」重新接入其内部解析模型的关键基础设施。
小结
string unescape是string escape的逆向命令,支持script(默认)、var、url三种风格,regex风格不支持解码;script走词法级还原(反斜杠转义、引号、通配符、变量与花括号展开标记),url走%XX解码,var走_XX_解码,三者底层实现分别在 crates/common/src/lib.rs 的unescape_string_internal、unescape_string_url、unescape_string_var;- 格式非法的输入被静默忽略,全部失败时命令返回状态 1;未知
--style值直接报Invalid style value错误; - 使用
string unescape --style=var (string escape --style=var $str)这类组合即可实现无损往返,相关用例可在 tests/checks/string.fish 中完整复现。
- CLI
- 开发工具
【免费下载链接】fish-shell
The user-friendly command line shell.
相关推荐
fish-shell 内置命令 echo 完全指南:输出文本、转义序列与参数行为详解
fish shell 内置命令 echo 完全指南:输出文本、转义序列与参数行为详解 导读 echo 是 fish shell 中最常用的内置命令之一,用于向终
CLI开发工具fish shell `string match` 命令详解:Glob 与 PCRE2 正则匹配实战指南
fish shell string match 命令详解:Glob 与 PCRE2 正则匹配实战指南 string match 是 fish shell 内置的
CLI开发工具fish-shell解析器:命令解析的实现原理
fish shell解析器:命令解析的实现原理 引言:为什么需要深入了解fish shell解析器? 你是否曾经在使用shell时遇到过命令语法错误,却不知道具
CLI开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考