ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

uutils 错误诊断详解:用编译器风格的 caret 报告定位 coreutils 命令行解析错误

uutils 错误诊断详解:用编译器风格的 caret 报告定位 coreutils 命令行解析错误 uutils 错误诊断详解用编译器风格的 caret 报告定位 coreutils 命令行解析错误【免费下载链接】coreutilsCross-platform Rust rewrite of the GNU coreutils项目地址: https://gitcode.com/GitHub_Trending/co/coreutils本篇技术指南讲解 uutilsGNU coreutils 的跨平台 Rust 重写中的**错误诊断error diagnostics**特性当test、chmod、sort、dd这类参数自成一种小语言的实用工具解析失败时uutils 会在终端上把命令行回显为源码行用 caret 精确定位出错的参数乃至参数内的某个字符并附带修复建议而在脚本、管道、测试框架等非交互场景下它依然输出与 GNU 完全一致的单行错误消息。读完本文你将理解该特性的触发条件、UUTILS_DIAG开关的用法、全部已支持工具的定位粒度以及它在uucore中的底层实现原理Snapshot、ariadne渲染器与三个共享解析器。为什么需要编译器风格的错误报告GNU coreutils 把每个错误都以 stderr 上的单行消息输出。这一行回答的是出了什么问题what却不说出在哪里where。对于参数本身构成一门小语言的实用工具——test表达式、chmod模式、sort的键key——用户真正关心的问题通常是到底是哪一个参数、甚至参数里的哪一个字符导致了解析失败。uutils 的解决方案是当 stderr 是终端时把这类错误渲染成编译器风格的报告——命令行被当作源码行回显一个 caret^类标记指向出错的部分通常还带一行Help建议。其他任何场景脚本、管道、测试框架都保留原来的单行消息因此所有读取 stderr 的程序都察觉不到差异。该特性的渲染样式定义在配套的 extensions-errors.css 中。逐工具对比Before 与 After下面每个实用工具先展示Before——当 stderr 不是终端时 uutils 输出的单行消息大多数情况下与 GNU 输出完全一致再展示After——在终端上渲染出的报告含颜色。test指向使表达式失败的参数$ test 7 -eq zap test: invalid integer zap在终端上报告会把命令行回显为源码行并用 caret 指向出错参数$ test 7 -eq zap test: invalid integer zap ╭─[ test:1:7 ] │ 1 │ 7 -eq zap │ ─── │ │ Help: -eq, -ne, -lt, -le, -gt and -ge compare integers; use , !, or to compare strings │ -eq equal, -ne not equal, -lt less than, -le less than or equal, -gt greater than, -ge greater than or equal ───╯报告头部的test:1:7含义是工具名test、第 1 行、字节偏移 7zap的起始位置。cut从长列表里挑出出错的那一项范围列表往往很长只有其中一项是错的。cut -f 1,4-2,9-12中4-2是递减范围$ cut -f 1,4-2,9-12 notes.txt cut: invalid decreasing range ╭─[ cut:1:10 ] │ 1 │ cut -f 1,4-2,9-12 notes.txt │ ─┬─ │ ╰─── this range ends before it starts │ │ Help: a list is N, N-M, N- or -M, separated by commas, as in -f1,4-6,9- ───╯注意标签labelthis range ends before it starts 是对单行消息的补充说明——它解释为什么而消息本身只陈述递减范围无效。expr非整数参数$ expr 9 foo expr: non-integer argument ╭─[ expr:1:5 ] │ 1 │ 9 foo │ ─── │ │ Help: arithmetic operators need integers; use or ! to compare strings instead ───╯chmodcaret 能指向参数内部的具体字符caret 甚至可以指向参数内部、恰好破坏解析的那个字符$ chmod grw?x notes.txt chmod: invalid operator (expected , -, or , but found ?) ╭─[ chmod:1:5 ] │ 1 │ grw?x notes.txt │ ─ │ │ Help: a mode is either octal, as in 644, or clauses such as urwx,go-w ───╯这里?被单独标出而grw与x notes.txt以普通样式显示。tr反向范围与修复建议$ tr qw[y-b] x tr: range-endpoints of y-b are in reverse collating sequence order ╭─[ tr:1:7 ] │ 1 │ tr qw[y-b] x │ ─┬─ │ ╰─── did you mean b-y? │ │ Help: a range goes from the lower character to the higher one, as in a-z ───╯did you mean b-y?是一个典型的修复建议型标签——它给出了用户真正想写的写法。csplit让正则引擎说出它卡在哪个字符模式本身是一门小语言而正则引擎恰好知道自己卡在了哪个字符上于是 caret 可以说出这一点$ csplit notes.txt /a{2,1}/ csplit: /a{2,1}/: invalid pattern ╭─[ csplit:1:20 ] │ 1 │ csplit notes.txt /a{2,1}/ │ ──┬── │ ╰──── invalid repetition count range, the start must be the end │ │ Help: a pattern is a line number N, /REGEXP/[OFFSET] or %REGEXP%[OFFSET], each optionally followed by {N} or {*} ───╯值得注意的细节这里的标签直接引用自正则引擎而非翻译因为这段措辞只存在于引擎中。sort键说明符中的杂散字符$ sort -k2.3x notes.txt sort: stray character in field spec: invalid field specification 2.3x ╭─[ sort:1:11 ] │ 1 │ sort -k2.3x notes.txt │ ─ │ │ Help: a key is FIELD[.CHAR][OPTS][,FIELD[.CHAR][OPTS]], as in -k2.3,4nr ───╯caret 精确落在多余的x上。numfmt格式指令中唯一的转换$ numfmt --format%q 1000 numfmt: invalid format %q, directive must be %[0][][-][N][.][N]f ╭─[ numfmt:1:18 ] │ 1 │ numfmt --format%q 1000 │ ┬ │ ╰── f is the only conversion numfmt has; %d, %e, %g and the other C conversions are not accepted │ │ Help: a format is [PREFIX]%[0][][-][WIDTH][.PRECISION]f[SUFFIX], as in %-10.2f ───╯标签点明了根本原因f是numfmt唯一支持的转换。printf格式串中的转换与转义$ printf %5.2c q printf: %5.2c: invalid conversion specification ╭─[ printf:1:8 ] │ 1 │ printf %5.2c q │ ───── │ │ Help: %d, %s, %x, %f and the other C conversions are accepted, plus %b and %q; a literal % is written %% ───╯残缺的转义同理printf a\xzb会把 caret 放在缺少十六进制数字的\x下面。env-S拆分字符串中的偏移env -S接收一整条命令行并像 shell 一样拆分它其错误消息会点名一个偏移量——这正是 caret 可以展示的东西$ env -S echo ${1FOO} env: only ${VARNAME} expansion is supported, error at: ${1FOO} ╭─[ env:1:14 ] │ 1 │ env -S echo ${1FOO} │ ─┬─ │ ╰─── a variable name cannot start with a digit │ │ Help: only $NAME and ${NAME} are expanded; the other shell forms are not ───╯注意-S字符串中含有空格因此被回显时带引号而 caret 依然能指向引号内部。${1被标出说明变量名不能以数字开头。dd键、值、标志位的三级定位dd的每个操作数都是KEYVALUE对而值本身又可能是逗号分隔的标志列表因此 caret 可以从三个层级中挑出对象键、整个值或列表中的单个标志。标志会在列表中它实际所处的位置被下划线标出而不是在其文本首次出现处建议信息会列出该操作数接受的全部标志$ dd convucase,zap dd: invalid conversion: zap ╭─[ dd:1:15 ] │ 1 │ dd convucase,zap │ ─┬─ │ ╰─── not a known conversion │ │ Help: conv is one of ascii, ebcdic, ibm, lcase, ucase, block, unblock, swab, sync, noerror, sparse, excl, nocreat, notrunc, fdatasync or fsync ───╯iflag与oflag被分开报告因此输出标志的错误不再被归咎于输入且各自列出自己的标志$ dd oflagzap dd: invalid output flag: zap ╭─[ dd:1:10 ] │ 1 │ dd oflagzap │ ─┬─ │ ╰─── not a known output flag │ │ Help: oflag is one of direct, directory, dsync, sync, nocache, nonblock, noatime, noctty, nofollow, append or seek_bytes ───╯未知的键被标出时不会连带上它的值——因为被拒绝的并非值$ dd zap1 dd: unrecognized operand zap1 ╭─[ dd:1:4 ] │ 1 │ dd zap1 │ ─── │ │ Help: an operand is KEYVALUE, as in iffile bs4k count10 ───╯放不下的数字会被拒绝而不是被悄悄截断clampcaret 覆盖整个值$ dd count99999999999999999999999 dd: invalid number: 99999999999999999999999: Value too large for defined data type ╭─[ dd:1:10 ] │ 1 │ dd count99999999999999999999999 │ ─────────────────────── │ │ Help: a number may be followed by a multiplier: c, w, b, then K, M, G and so on for 1024, kB, MB, GB for 1000 ───╯兼容性原则严格意义上的交互便利该特性被明确定位为交互式便利任何读取输出的程序都无法察觉差异。uutils 严格遵循以下约定只在 stderr 是终端时渲染报告除非UUTILS_DIAG另有指示见下节。在脚本、管道或测试框架中工具依然打印原来的单行消息因此依赖匹配 stderr 的既有脚本完全不受影响。只有错误能定位到命令行上的某处时才绘制报告。当无法定位时——错误不针对任何单个参数或所点名的操作数在被定位之前已被改写、消耗——即使在终端也打印单行消息。因此下表中每个工具报告的是它能定位的解析错误而非它的全部错误。退出码不变Try ... --help提示也不变如果错误属于用法错误usage error它仍会打印在报告下方。颜色遵循惯例只在终端上使用且遵循NO_COLOR环境变量约定设置后禁用颜色。所有消息、标签与帮助行都与 uutils 其余部分一样做了本地化参见 Localization。渲染可以被完全编译掉它位于feat_diagnosticscargo feature 之后该 feature 默认开启。用--no-default-features外加feat_os_*选择构建即可去掉渲染器及其ariadne依赖每个工具保留其单行消息。打开与关闭UUTILS_DIAG开关默认逻辑仅看 stderr 是否为终端其余一概不问——这通常正是你想要的但并非总是如此。UUTILS_DIAG环境变量可以覆盖这一判断取值效果always即使 stderr 是文件或管道也绘制报告。never即使在终端也保留单行消息。auto、未设置、其他任意值按上述规则由 stderr 决定。无法识别的值刻意不构成错误——这类变量往往在 shell 配置文件中导出一次后就被遗忘任何拼写都不应该让工具失败。always适用于错误必须离开它发生的终端的情况——比如写入 CI 日志或粘贴到 bug 报告中$ UUTILS_DIAGalways sort -k2.3x notes.txt 2 parse.log颜色是另一个独立问题且仍然由终端回答被强制写入文件的报告不带颜色因此不必有人去剥离转义序列。NO_COLOR是终端上的中间设置——报告仍然绘制只是纯文本。不设该变量时两个方向都只需一条命令即可达到。想在终端上拿到单行消息就把 stderr 送到一个不是终端的地方$ sort -k2.3x notes.txt 21 | cat sort: stray character in field spec: invalid field specification 2.3x反之想让必须在真实终端下运行的命令产生报告可以用script -qec ... /dev/null给它一个 pty或用 expect 的unbuffer。已支持的实用工具总表下表列出当前支持诊断报告的工具、caret 指向的对象以及对应的演示命令工具caret 指向演示命令test使表达式失败的参数test 7 -eq zapexpr使表达式失败的参数expr 9 foochmod非法符号/八进制模式中失败的子句或字符chmod grw?x fruits.txtmkdir-m/--mode给出的模式中失败的部分mkdir -m uq mydirmkfifo-m/--mode给出的模式中失败的部分mkfifo -m uq mypipemknod-m/--mode给出的模式中失败的部分mknod -m uq mydev c 1 3install-m/--mode给出的模式中失败的部分install -m uq fruits.txt desttr集合中出错的部分坏类别、反向范围、坏重复计数等tr qw[y-b] xsort-k/--key或字段说明符、或-S的 SIZE 中失败的部分sort -k2.3x fruits.txtnumfmt--field或--format说明符中失败的部分numfmt --format%q 1000printf格式串中失败的转换或转义printf %5.2c qseq-f/--format格式中失败的转换seq -f %5.2c 1 3stat-c/--format或--printf格式中失败的指令stat -c %d%.3 fruits.txtenv-S/--split-string字符串中失败的部分env -S echo ${1FOO}ddKEYVALUE操作数中失败的键、值或标志dd convucase,zapjoin-o输出格式中失败的字段join -o 1.2,2.x fruits.txt fruits.txtcut-b、-c、-f或-F列表中的失败范围cut -f 1,4-2 fruits.txtcsplit失败的模式操作数、其正则中出错的字符、或-b/-n格式csplit fruits.txt /a(b/split-b、-C或-l的 SIZE 中失败的部分split -b 7zq fruits.txtshred-s/--size的 SIZE 中失败的部分shred -s 4vv fruits.txthead-c或-n的 SIZE 中失败的部分head -c 1fb fruits.txttail-c或-n的 SIZE 中失败的部分tail -c 1fb fruits.txttruncate-s/--size的 SIZE 中失败的部分truncate -s 10fb fruits.txtod-j、-N、-S或-w的 SIZE 中失败的部分od -N 3zz fruits.txtdu-B/--block-size或-t/--threshold的 SIZE 中失败的部分du -B 1fbdf-B/--block-size的 SIZE 中失败的部分df -B 1fbls--block-size的 SIZE 中失败的部分dir与vdir同样支持ls --block-size1fbstdbuf-i、-o或-e的缓冲模式中失败的部分stdbuf -o 6pq head从表可以看出SIZE数字单位解析错误的支持面最广head/tail/split/truncate/od/du/df/ls/stdbuf等其次是模式解析chmod/mkdir/mkfifo/mknod/install。工作原理uucore::features::diagnostics渲染逻辑位于uucore::features::diagnostics实现在 diagnostics.rs构建在ariadne库之上。在 features.rs 中可以看到该模块由diagnosticscargo feature 门控开启时编译真实模块关闭时编译 diagnostics_stub.rs 这个无操作替身——它镜像了真实 APIenabled()恒为false、定位器找不到任何东西、渲染方法不渲染使所有调用方无需任何cfg即可编译并回退到各自的单行消息。diagnostics_boundary模块如char_span、floor_boundary、list_items等边界计算即使在渲染被编译掉时也是真实生效的因此被两个模块共享。选择加入该特性的工具遵循三步流程对其参数列表拍一张Snapshot。快照把参数拼成一行需要时加引号非 UTF-8 参数按 shell 的方式渲染并记住每个参数的字节范围。由于解析会移动、改写或消耗参数列表工具必须在解析开始前调用capture或operands后者会去掉argv[0]程序名把副本留好capture内部先检查enabled()诊断关闭时返回None因此这个副本只在确实要渲染时才付出代价。把自身错误类型映射为位置以及可选的标签与一行建议放在每个工具各自的diagnostics.rs小模块中如 csplit/src/diagnostics.rs、numfmt/src/diagnostics.rs。标签只在它补充了消息未说出的内容时才使用——一个期望或一个修复如tr的did you mean b-y?——绝不重复消息本身没有标签时跨度被绘制为纯下划线。所有面向用户的内容都以已本地化的形式传入。定位操作数来自的参数用Snapshot::index_of_value定位某选项的值无论以何种拼写给出-k 2.3x、-k2.3x、-rk2.3x、--key 2.3x、--key2.3x均可用Snapshot::index_of_positional定位位置操作数或用工具自己跟踪的索引然后调用Snapshot::render指向整个参数或Snapshot::render_inside_at指向操作数携带的内部字节范围。因为定位基于点名而非搜索恰好与操作数文本相同的文件名、其他选项或程序名永远不可能把 caret 引走。Snapshot::render_option_value一步完成这两件事适合大多数选项值。含空格的参数回显时会加引号caret 依然指向内部引号只包裹操作数其字节仍按打印位置找到偏移从那里起算。无法按原样打印的参数非 UTF-8或引号必须被打断的则整体加下划线因为对它的任何偏移都无法与读者所见对齐。调用方首先检查diagnostics::enabled()其实现见 diagnostics.rs读一次UUTILS_DIAG未设置时用std::io::stderr().is_terminal()判断因此非交互路径零开销并保持 GNU 兼容的消息。共享解析器的统一处理来自共享解析器的错误由共享辅助函数渲染这样多个工具共用的同一语法在所有工具中解释方式一致其标签放在公共语言环境字符串中而非每个工具重复一份。目前有三个解析器采用这种模式模式Modes——位于uucore::modemode.rs服务于chmod、mkdir、mkfifo、mknod和install。以它作为工作示例解析器以携带出错字节范围的ModeError报告错误其渲染为每个接受模式的工具放置 caret——ModeError::render_option_value把模式定位为mkdir/mkfifo/mknod/install的-m/--mode值而chmod接受 clap 无法识别的模式形式如chmod -w -r file自己跟踪每个模式操作数的位置并把索引传给ModeError::render_at。范围列表Range lists——位于uucore::rangesranges.rs服务于cut的-b、-c、-f以及numfmt --field。Range::from_list报告列表中哪一项失败以及它所在的位置。大小Sizes——位于uucore::parser::parse_sizeparser 模块目前服务于head、tail、truncate、split、shred、stdbuf、sort、od、du、df和ls含dir与vdir并可供该解析器的其他调用方使用。ParseSizeError::span从操作数推算出被拒绝的是两部分中的哪一部分——数字还是单位——因此错误类型保持了其调用方手工构建的形状。构建与特性门控在根 Cargo.toml 中可以看到该特性在 workspace 层面的定义feat_diagnostics [uucore/diagnostics]默认开启注释明确说明用--no-default-features外加feat_os_*选择编译即可去掉渲染器及其 ariadne 依赖每个工具保留其单行消息ariadne以0.6.0锁定在 workspace 依赖中并在 src/uucore/Cargo.toml 中通过diagnostics [dep:ariadne]变成可选依赖。此外注释还提到诊断会按行号重写 ariadne 的输出因此存在一个针对该重写假设的回归测试the_rendered_report_has_the_row_shape_the_rewriting_assumes。测试验证测试侧同样按 feature 门控例如 test_dd.rs 中的diagnostics测试模块以#[cfg(all(feature feat_diagnostics, not(wasi_runner)))]条件编译并专门覆盖UUTILS_DIAG的三种取值——always强制渲染、never强制单行与sometimes验证未知取值回退到自动判断test_chmod.rs 与 test_cut.rs 也各有模式感知的诊断测试模块验证 caret 定位的准确性。小结uutils 的错误诊断特性在GNU 兼容与交互体验之间划出了一条清晰的界线非交互场景下输出与 GNU 逐字节兼容的单行消息交互终端下则提供编译器风格的 caret 报告精确定位test表达式、chmod模式、sort键、dd操作数乃至tr字符范围中出错的精确位置。它通过feat_diagnosticsfeature 可选编译、通过UUTILS_DIAG环境变量可调并借助uucore中模式、范围、大小三个共享解析器让同类语法在所有工具中获得一致的诊断体验——这一设计为大型命令行工具的错误报告提供了一个值得借鉴的范本。【免费下载链接】coreutilsCross-platform Rust rewrite of the GNU coreutils项目地址: https://gitcode.com/GitHub_Trending/co/coreutils创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表