- CLI
- 开发工具
【免费下载链接】fish-shell
The user-friendly command line shell.
导读
string length是 fish-shell(用户友好的命令行 shell,源码仓库位于src/builtins/string/length.rs)内置string命令家族中负责测量字符串长度的子命令。它既能在普通模式下按“字符(character)”逐一统计每个参数的长度,也能通过--visible切换到“可见宽度(visible width)”模式,精确模拟字符串在当前终端中实际占据的列数——这包括剔除 ANSI 转义序列、依据$fish_emoji_width与$fish_ambiguous_width处理宽字符、并按换行与回车分段取最大宽度。读完本篇,你将掌握string length的全部选项、退出状态语义、与test -n的等价关系,以及其底层宽度计算在 src/builtins/string.rs 中的实现原理。
命令语法与选项
string length的完整调用形式如下(引自 doc_src/cmds/string-length.rst 的 Synopsis 部分):
string length [-q | --quiet] [-V | --visible] [STRING ...]它接受两个互不排斥的开关选项,二者均可与位置参数组合使用:
| 选项 | 含义 |
|---|---|
-q,--quiet | 静默模式,不输出任何长度数值,仅以退出状态表达结果 |
-V,--visible | 可见宽度模式,按字符串在当前终端中实际占据的列数(visible width)统计,而非字符数 |
参数[STRING ...]支持零个或多个字符串;当没有给出任何字符串、或给出的字符串均为空时,命令按“无有效结果”处理(见下文退出状态)。
从源码实现看,这两个选项由 src/builtins/string/length.rs 中的Length结构体解析:
const LONG_OPTIONS: &'static [WOption<'static>] = &[ wopt(L!("quiet"), NoArgument, 'q'), wopt(L!("visible"), NoArgument, 'V'), ]; const SHORT_OPTIONS: &'static wstr = L!("qV");即quiet对应短选项-q,visible对应短选项-V,两者均为“不带参数”的标志位,任何其他选项都会触发UnknownOption错误。
普通模式:按字符计数
不携带任何选项时,string length逐个统计每个字符串参数的字符(character)个数,每个参数输出一行整数结果。
>_ string length 'hello, world' 12这里"hello, world"包含 12 个字符(含空格与逗号),输出12。值得强调的是,“字符”是按 Unicode 码点层面统计的:é、中、emoji 等非 ASCII 字符各计一个字符。仓库单元测试 src/builtins/string/length.rs 验证了这一点:
validate!(["string", "length", "\u{2008}A"], STATUS_CMD_OK, "1\n"); // 字符 U+2008 计为 1 validate!(["string", "length", "um", "dois", "três"], STATUS_CMD_OK, "2\n4\n4\n");其中"três"(tr+ê+s)输出4,证实每个 Unicode 字符都按一个字符计数;um、dois分别输出2、4。多参数时每个参数独立输出一行,顺序与参数顺序一致。
多个参数依次输出:该行为也可在仓库集成测试 tests/checks/string.fish 中看到(如string length "hello, world"的用例)。普通模式不会做终端相关的宽度计算,因此性能开销低,适合脚本中对字符串进行快速非空或长度判断。
--quiet模式与退出状态语义
string length的退出状态规则是它区别于wc -c、wc -m等外部工具的关键:
- 退出状态 0:至少有一个非空(长度大于 0)的字符串参数被给出;
- 退出状态 1:所有参数都为空、或根本没有参数。
该语义在源码 src/builtins/string/length.rs 中以nnonempty计数器实现:只有当nnonempty > 0时返回成功Ok(()),否则返回STATUS_CMD_ERROR。
-q/--quiet模式正是围绕这一退出状态设计的“谓词(predicate)”用法:它不打印任何长度数字,只通过退出状态告诉你“是否存在非空字符串”,从而等价于test -n "$str":
>_ set str foo >_ string length -q $str; echo $status 0 # Equivalent to test -n "$str"配合and/or可以写出更符合 fish 习惯的条件判断,例如 tests/checks/string.fish 中的写法:
string length -q ""; and echo not zero length; or echo zero length单元测试也逐一固化了退出状态行为 src/builtins/string/length.rs:
| 输入 | 退出状态 | 输出 |
|---|---|---|
string length(无参数) | 1(STATUS_CMD_ERROR) | 无输出 |
string length "" | 1 | 0 |
string length "" "" "" | 1 | 0\n0\n0 |
string length a | 0 | 1 |
string length -q | 1 | 无输出 |
string length -q "" | 1 | 无输出 |
string length -q a | 0 | 无输出 |
注意即使为空字符串,非静默模式仍会输出0这一行,只是最终退出状态为 1。-q模式下则完全不输出,仅由$status承载结论。
--visible模式:测量终端真实占用宽度
-V/--visible模式下,string length统计的不是字符数,而是字符串在当前终端中会占据的列数(columns)。文档 doc_src/cmds/string-length.rst 给出的语义要点包括:
- 剔除 fish 认识的转义序列:像
set_color输出的 ANSI 颜色码不会被计入宽度; - 考虑
$fish_emoji_width与$fish_ambiguous_width:emoji 与“宽度不明”字符按这两个变量的取值决定宽 1 列还是 2 列; - 按
\n逐行独立计数:多行输入会为每一行分别输出一个宽度; - 按
\r取一行中最宽的一段:回车会“回到行首”,因此一行最终宽度取该行内最长段落的列数。
其目的正如文档所述:测量 STRING 在当前终端中实际占据的列数,用于对齐、画表格、计算进度条等对“视觉宽度”敏感的脚本场景。
剔除转义序列:set_color不计入宽度
>_ string length --visible (set_color red)foobar # the set_color is discounted, so this is the width of "foobar" 6(set_color red)生成 ANSI 颜色转义序列,虽然序列中可能含可打印字符(如[、3、1、m),但它们不会被终端渲染出来,因此被--visible扣除,最终宽度等于纯文本foobar的 6。集成测试 tests/checks/string.fish 中的string length --visible (set_color red)abc同样验证了这一点。
emoji 与模糊宽度字符:$fish_emoji_width/$fish_ambiguous_width
>_ string length --visible 🐟🐟🐟🐟 # depending on $fish_emoji_width, this is either 4 or 8 # in new terminals it should be 8四个鱼 emoji(🐟)在$fish_emoji_width被设为 2 时输出 8,若该变量被设为 1 则输出 4。默认行为取决于终端与 locale 探测结果,fish 会在启动时依据当前终端能力为$fish_emoji_width与$fish_ambiguous_width设定合适的默认值。这两个变量的解析与默认值处理位于 src/env_dispatch.rs:fish_emoji_width可由用户覆盖(代码中会打印 "Overriding default fish_emoji_width w/ ..." 日志),fish_ambiguous_width则读取变量并调用fish_wcstoi解析为整数。它们的取值最终通过fish_wcwidth_visible影响每个字符的列宽计算(见下一节)。
回车\r:按行内最宽段落计宽
>_ string length --visible abcdef\r123 # this displays as "123def", so the width is 6 6回车符使终端光标回到行首,123覆盖了abcdef的前三个字符,因此视觉上显示为123def,占据 6 列。--visible将一行按\r拆成若干段(abcdef与123),取其中最宽的一段长度(max 6)。
换行\n:逐行独立输出
>_ string length --visible a\nbc # counts "a" and "bc" as separate lines, so it prints width for each 1 2包含换行符的输入会被拆分为多行,每行单独输出一个宽度:a为 1,bc为 2。这意味着该命令天然适合处理多行字符串的“逐行视觉宽度”需求。
源码级原理:--visible的宽度计算
--visible的核心逻辑位于 src/builtins/string/length.rs 的handle方法:
if self.visible { // Visible length only makes sense line-wise. for line in arg.split('\n') { let mut max = 0; // Carriage-return returns us to the beginning. The longest substring without // carriage-return determines the overall width. for reset in line.split('\r') { let n = width_without_escapes(reset, 0); max = usize::max(max, n); } if max > 0 { nnonempty += 1; } if !self.quiet { streams.out.appendln(&max.to_wstring()); } else if nnonempty > 0 { return Ok(()); } } }流程可拆解为:先按\n切成“行”,再对每行按\r切成“段”,每段调用width_without_escapes计算去转义后的可见宽度,行内取最大值作为该行宽度;随后逐行输出(--quiet时一旦发现任一非空行就提前返回成功)。这里也可以看到-q与-V可以叠加使用:-qV表示“是否存在可见宽度大于 0 的内容”。
真正执行“宽度”计算的函数是 src/builtins/string.rs 中的width_without_escapes,其实现要点如下:
- 遍历每个字符,用
fish_wcwidth_visible(c)累加列宽——该函数综合了$fish_emoji_width、$fish_ambiguous_width与底层 wcwidth 能力; - 再扫描
\x1B(ESC)开头的 ANSI 转义序列,调用escape_code_length(位于 src/screen.rs)判断其长度,并将其内部所有可打印字符的宽度从总数中扣除,避免颜色码“虚增”宽度; - 处理连续/嵌套转义(如 xterm 的 SGR0 reset 由
\e(B\e[m两段组成),跳过整段后继续扫描; - 最终
usize::try_from(width)将累计值转为无符号整数返回。
另外值得注意的是,width_without_escapes同样被string pad(src/builtins/string/pad.rs)与string shorten(src/builtins/string/shorten.rs)复用——这三者共同构成 fish 中“按可见宽度对齐与截断”的底层基础设施,因此string length -V的测量结果与string pad、string shorten的对齐效果是严格一致的,你可以放心用同一套宽度口径做字符串排版。
实战示例与使用建议
判断变量是否非空
set str foo if string length -q $str echo "str 非空" end与test -n "$str"等价,但更贴近 fish 的字符串管道风格;注意当$str未定义时,fish 会把它展开为空参数,此时string length -q返回 1,分支不会执行——这一行为在逻辑上是安全的。
对齐输出:测量可见宽度
set colored (set_color green)OK(set_color normal) set width (string length -V -- $colored) echo "提示信息占 $width 列"普通模式会把 ANSI 颜色序列的字符也算进去导致“看起来比实际宽”,-V模式则给出真实列数,可配合printf的%*s或string pad做对齐。
校验用户输入的多行文本
string length -V -- $multiline # 每行输出一个宽度逐行输出宽度,便于确认最长一行的列数是否超过终端宽度上限,避免换行错乱。
关于--与特殊字符
fish 的string子命令按位置参数解析,若字符串以-开头建议在其前使用--明确终止选项解析,避免被误当作-q/-V。例如string length -- -foo。string length -V -这类写法不会匹配任何选项,也会被安全地当作普通字符串处理。
小结
string length以极小的语法面(-q/-V两个开关)覆盖了两类常见需求:普通模式下按字符计数并给出“是否存在非空参数”的退出状态(等价于test -n);可见宽度模式下则忠实反映字符串在终端中的实际列数,正确扣除 ANSI 转义序列、尊重 emoji 与模糊宽度字符变量、并妥善处理\n与\r带来的换行与覆盖语义。其实现与string pad、string shorten共享同一套宽度计算函数,在 fish 的文本排版工具链中扮演“测量基准”的角色。相关的单元测试(src/builtins/string/length.rs)与集成测试(tests/checks/string.fish)可作为进一步验证与学习该命令行为的第一手资料。
- CLI
- 开发工具
【免费下载链接】fish-shell
The user-friendly command line shell.
相关推荐
fish-shell `string pad` 完全指南:按可见宽度对齐与填充字符串
fish shell string pad 完全指南:按可见宽度对齐与填充字符串 本文围绕 fish shell 内置命令 string pad 展开,讲解如何
CLI开发工具fish-shell `return` 命令完全指南:函数退出、退出状态与脚本控制流
fish shell return 命令完全指南:函数退出、退出状态与脚本控制流 return 是 fish shell 中用于终止当前函数执行、并可选地设置退
CLI开发工具fish-shell 的 string join 与 string join0 命令:用分隔符拼接字符串的完整指南
fish shell 的 string join 与 string join0 命令:用分隔符拼接字符串的完整指南 导读 string join 与 strin
CLI开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考