エラー箇所を指し示す:uutils coreutils におけるコンパイラ風診断メッセージ
Pointing at the error: compiler-style diagnostics in uutils coreutils (uutils.org)
要約
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 ビルドで出荷されていないユーティリティ用なので、ローカルで試してください。
| Utility | What the caret points at |
|---|---|
test | the argument that made the expression fail |
expr | the argument that made the expression fail |
chmod | the failing clause (or character) of an invalid symbolic or octal mode |
mkdir | the failing part of the mode given to -m/--mode |
mkfifo | the failing part of the mode given to -m/--mode |
mknod | the failing part of the mode given to -m/--mode |
install | the failing part of the mode given to -m/--mode |
tr | the part of a set that is at fault (bad class, backwards range, bad repeat count, …) |
sort | the failing part of a -k/--key or field specification, or of the SIZE given to -S |
numfmt | the 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 |
printf | the failing conversion or escape in the format string |
seq | the failing conversion in the format given to -f/--format |
stat | the failing directive of a -c/--format or --printf format |
env | the failing part of a -S/--split-string string |
dd | the failing key, value or flag of a KEY=VALUE operand |
join | the failing field of the output format given to -o |
cut | the failing range in the list given to -b, -c, -f or -F |
csplit | the failing pattern operand, the character of its regex that broke, or the format given to -b/-n |
split | the failing part of the SIZE given to -b, -C or -l |
shred | the failing part of the SIZE given to -s/--size |
head | the failing part of the SIZE given to -c or -n |
tail | the failing part of the SIZE given to -c or -n |
truncate | the failing part of the SIZE given to -s/--size |
od | the failing part of the SIZE given to -j, -N, -S or -w |
du | the failing part of the SIZE given to -B/--block-size or -t/--threshold |
df | the failing part of the SIZE given to -B/--block-size |
ls | the failing part of the SIZE given to --block-size (also dir and vdir) |
stdbuf | the 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_diagnosticscargo 機能(デフォルトでオン)の背後にあり、それなしでビルドすると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 なども、指し示すための同じ正規表現とフォーマット文字列を持っています。
使用しているユーティリティがまだ役に立たない単一行メッセージを出力する場合は、パッチを歓迎します!