HN 日本語サマリー

← 一覧へ戻る
プログラミング

エラー箇所を指し示す:uutils coreutils におけるコンパイラ風診断メッセージ

Pointing at the error: compiler-style diagnostics in uutils coreutils (uutils.org)

61 pointsby ingve8 コメント

要約

uutils coreutils は、コマンドラインツールのエラー報告方法を刷新し、コンパイラのような詳細な診断メッセージを導入しました。これにより、エラーが発生した正確な引数や文字位置を、ケアット(^)記号とヘルプメッセージで分かりやすく示せるようになります。この機能は、tr、cut、chmod、sort、env、test、head、numfmt、csplit など、28のユーティリティで利用可能になり、ユーザーはエラーの原因を迅速に特定できるようになります。

全文翻訳

Pointing at the error: compiler-style diagnostics in uutils coreutils

Aug 31, 2026

Coreutils は 50 年にわたり進化を続けてきました。今、私たちはエラー報告の方法を再考することで、その革新をさらに推し進めています。

Unix ツールは、エラーを stderr に単一行で報告します。その行は、何が起こったかは伝えますが、どこで起こったかは伝えません。ほとんどのコマンドには他に指し示す場所はありませんが、test 式、chmod モード、sort キー、tr セットのような小さな言語を引数として取るコマンドがいくつかあります。それらのパースに失敗した場合、実際に知りたいのは、パーサーがどの引数、あるいはその引数のどの文字でつまずいたかということです。

rustc は長年、キャレット('^')でその質問に答えてきました。そして ariadne は、同じレンダリングを 1 つの依存関係で提供します。コマンドラインツールにそれを導入するというアイデアは uutils awk から来ており、awk プログラムのエラーはすでにそのように報告されています。coreutils の引数はより小さな言語ですが、パースは同じように行われます。0.11.0 から、coreutils はそれを使用しています。stderr がターミナルである場合、パースエラーはレポートとして表示されます。引数はソース行としてエコーバックされ、キャレットが原因を指し示し、有用な情報があればヘルプ行が構文を説明します。

外観

まず tr から始めましょう。GNU のメッセージは、あなたがすでに照合順序(collating sequence)が何であるかを知っていることを前提としています。

Before:

tr

tr: range-endpoints of 'y-b' are in reverse collating sequence order

After:

tr

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
───╯

プレイグラウンドで試す。

cut のリストは長くなることがあり、その中に不正な項目が 1 つだけ含まれている場合があります。

Before:

cut

cut: invalid decreasing range
Try 'cut --help' for more information.

After:

cut

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-
───╯
Try 'cut --help' for more information.

プレイグラウンドで試す。

キャレットは引数全体をカバーする必要はありません。1 文字を指すこともできます。

Before:

chmod

chmod: invalid operator (expected +, -, or =, but found ?)

After:

chmod

chmod: invalid operator (expected +, -, or =, but found ?)
   ╭─[ chmod:1:5 ]
   │
 1 │ g+rw?x notes.txt
   │     ─
   │
   │ Help: a mode is either octal, as in 644, or clauses such as u+rwx,go-w
───╯

sort のキーは短いため、余分な文字を見落としやすいです。

Before:

sort

sort: stray character in field spec: invalid field specification '2.3x'

After:

sort

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
───╯

プレイグラウンドで試す。

env -S はシェルが分割するような方法でコマンドライン全体を受け取ります。古いメッセージは、問題のあるフラグメントを引用して返すことしかできませんでした。文字列にはスペースが含まれているため、引用符で囲んでエコーバックされ、キャレットは引用符の中に収まります。

Before:

env

env: only ${VARNAME} expansion is supported, error at: ${1FOO}

After:

env

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
───╯

test は、式を個別の引数で構築します。レポートは test を付けずに式自体をエコーバックし、それを壊した引数をマークします。

Before:

test

test: invalid integer 'zap'

After:

test

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
───╯

プレイグラウンドで試す。

SIZE は数値と単位で構成されます。レポートは、どちらの部分が拒否されたかを示します。

Before:

head

head: invalid number of bytes: '1fb'

After:

head

head: invalid number of bytes: '1fb'
   ╭─[ head:1:10 ]
   │
 1 │ head -c 1fb notes.txt
   │          ─┬
   │           ╰── not a known unit
   │
   │ Help: a size is a number and an optional unit: K, M, G and so on for 1024, KB, MB, GB for 1000
───╯

プレイグラウンドで試す。

suite 内のすべての SIZE は 1 つのパーサーで処理されるため、tail -c、truncate -s、split -b、shred -s、od -N、sort -S、du -B、df -B、ls --block-size のブロックサイズ、および du -t のしきい値でも同じレポートが表示されます。

numfmt --format は printf スタイルのフォーマットで、変換は 1 つだけ許可されます。古いメッセージは単にルールを繰り返すだけでした。アノテーションは、実際に記述した変換の名前を示します。

Before:

numfmt

numfmt: invalid format '%q', directive must be %[0]['][-][N][.][N]f

After:

numfmt

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"
───╯

プレイグラウンドで試す。

csplit のパターンには正規表現が含まれており、正規表現エンジンはどの文字で失敗したかを知っています。私たちは単にその位置情報を破棄していました。

Before:

csplit

csplit: '/a{2,1}/': invalid pattern

After:

csplit

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 {*} 
───╯

プレイグラウンドで試す。

そのラベルは正規表現エンジンから直接取得され、その単語が存在する唯一の場所であるため、翻訳されません。

適用範囲

0.11.0 では 28 のユーティリティがこれを使用しています。リンクされた例はプレイグラウンドで実行できます。それ以外のものは、WebAssembly ビルドで出荷されていないユーティリティ用なので、ローカルで試してください。

UtilityWhat the caret points at
testthe argument that made the expression fail
exprthe argument that made the expression fail
chmodthe failing clause (or character) of an invalid symbolic or octal mode
mkdirthe failing part of the mode given to -m/--mode
mkfifothe failing part of the mode given to -m/--mode
mknodthe failing part of the mode given to -m/--mode
installthe failing part of the mode given to -m/--mode
trthe part of a set that is at fault (bad class, backwards range, bad repeat count, …)
sortthe failing part of a -k/--key or field specification, or of the SIZE given to -S
numfmtthe failing part of a --format or --field specification, the value given to --from, --to, --from-unit, --to-unit, --padding or --header, or the input number itself
printfthe failing conversion or escape in the format string
seqthe failing conversion in the format given to -f/--format
statthe failing directive of a -c/--format or --printf format
envthe failing part of a -S/--split-string string
ddthe failing key, value or flag of a KEY=VALUE operand
jointhe failing field of the output format given to -o
cutthe failing range in the list given to -b, -c, -f or -F
csplitthe failing pattern operand, the character of its regex that broke, or the format given to -b/-n
splitthe failing part of the SIZE given to -b, -C or -l
shredthe failing part of the SIZE given to -s/--size
headthe failing part of the SIZE given to -c or -n
tailthe failing part of the SIZE given to -c or -n
truncatethe failing part of the SIZE given to -s/--size
odthe failing part of the SIZE given to -j, -N, -S or -w
duthe failing part of the SIZE given to -B/--block-size or -t/--threshold
dfthe failing part of the SIZE given to -B/--block-size
lsthe failing part of the SIZE given to --block-size (also dir and vdir)
stdbufthe failing part of the buffering mode given to -i, -o or -e
Compatibility first

GNU coreutils のドロップインリプレイスメントであることが最優先事項であるため、これは純粋にインタラクティブな利便性です。

  • レポートは stderr がターミナルである場合のみ 表示されます。スクリプト、パイプ、テストスイートでは、各ユーティリティは例の「Before」に示されているような、従来の単一行メッセージをそのまま出力し続けるため、stderr を grep するものはすべて引き続き機能します。
  • 終了コードは変更されません。
  • 色はターミナルでのみ使用され、NO_COLOR を尊重します。
  • uutils の他の部分と同様に、メッセージ、ラベル、ヘルプ行はローカライズされており、翻訳は Weblate で管理されています。
  • スペースを節約するためにコンパイルアウトすることも可能です。レンダリングは feat_diagnostics cargo 機能(デフォルトでオン)の背後にあり、それなしでビルドすると ariadne 依存関係が削除され、すべてのユーティリティは従来のプレーンメッセージを維持します。
オンとオフの切り替え

デフォルトでは、レンダリングは stderr がターミナルであるかどうかに基づいて決定され、それ以外は何も考慮されません。これは通常、望ましい結果ですが、常にそうとは限りません。UUTILS_DIAG 環境変数でこれを上書きできます。always はファイルやパイプにもレポートを描画し、never はターミナルでもプレーンな行を維持し、auto または変数が設定されていない場合は、以前と同様に stderr から決定します。認識されない値は意図的にエラーとはしません。これは、人々がシェルプロファイルから一度エクスポートして忘れるような変数であり、タイプミスがあってもユーティリティが失敗する原因にはなりません。

コマンドラインフラグはありません。最もフラグが必要なユーティリティにはそれを持たせることができません。test、printf、expr では、新しいオプションは不正であるか、オペランド自体と曖昧になります。

スクリプトや CI ログからレポートを取得して、例えばバグレポートに貼り付けるには、次のようにします。

$ UUTILS_DIAG=always sort -k2.3x notes.txt 2> parse.log
$ cat parse.log
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
───╯

色は個別に決定され、依然としてターミナルによって制御されます。ファイルに強制されたレポートは色なしで書き込まれるため、ストリップバックアウトする必要のあるエスケープシーケンスはありません。NO_COLOR はターミナルの中間に位置し、レポートはプレーンテキストで描画されます。

変数が導入される前に人々が使用していた 2 つのトリックは、依然として機能します。ターミナルではない場所に stderr を送信すると、プレーンな行が得られます。

$ sort -k2.3x notes.txt 2>&1 | cat
sort: stray character in field spec: invalid field specification '2.3x'

また、コマンドに pty を与えること(script -qec "sort -k2.3x notes.txt" /dev/null、または expect の unbuffer)でレポートを取得できます。これは、コマンドが他の理由でターミナル下で実行する必要がある場合に便利です。

次に来るもの

coreutils がこの機能の開始点であり、終着点ではありません。レンダリングは uucore::diagnostics にあり、他の uutils プロジェクトはすでにそれに依存しているため、採用するのは主にパーサーのエラーにスパンを渡す作業です。findutils と sed が現在配線されています。find 式と sed スクリプトは、キャレットが役立つまさにその種の小さな言語であり、grep、awk なども、指し示すための同じ正規表現とフォーマット文字列を持っています。

使用しているユーティリティがまだ役に立たない単一行メッセージを出力する場合は、パッチを歓迎します!