wc -lで数えているのは行数じゃなくて改行文字の数だった

  • Linux

この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。

wc -l でファイルの行数を数えるのは普段からよくやる。けど中身をちゃんと調べたら、「行数」じゃなくて「改行文字の数」を数えてるだけだった。この違いがハマりどころになる。

基本の出力順序

オプションなしで wc file.txt を実行すると、改行数・単語数・バイト数の3つが順番に出る。複数オプションを指定した場合も、指定順には関係なく「改行・単語・文字・バイト・最長行」の順で固定される。wc --bytes --words みたいに逆順で書いても、出力は変わらない。

各オプションの中身

  • -l (--lines): 改行文字(\n)の数を数える。行数そのものではない。
  • -w (--words): 空白で区切られた非空文字の連続を「単語」として数える。
  • -c (--bytes): バイト数。
  • -m (--chars): 文字数。
  • -L (--max-line-length): ファイル中の最も長い行の文字数(表示幅)。

オプション名は--linesなのに、実装は「改行文字の数」を数えているだけ。POSIXの仕様でも「the number of newline characters」と定義されていて、「行数」とは書いてない。ファイルの行数=改行の数、という前提は「各行の末尾に改行がある」場合にしか成り立たない。

-cと-mの違い

ASCIIだけのテキストなら -c と -m は同じ値になる。1文字1バイトだから。でもUTF-8のマルチバイト文字(日本語とか絵文字とか)を含むファイルだと、バイト数のほうが文字数より大きくなる。文字数を知りたいときは -m を使うのが正しい。

改行なしファイルの罠

-l が「改行文字の数」を数えているだけ、というのがはっきり分かるのがこのケース。

ターミナル
printf "hello" | wc -l
# 0
printf "hello" | wc -w
# 1

末尾に改行がない「hello」という文字列は、単語としては1個あるのに、行数としては0とカウントされる。テキストエディタで保存すると普通は末尾に改行が付くから気づきにくいけど、printf やスクリプトの出力をそのままパイプすると起きる。実機未確認だけど、curlのレスポンスをそのまま wc -l に食わせて「0行なのに中身はある」ってなるパターンもありそう。

ls | wc -l の弱点

ディレクトリ内のファイル数を数えるのに ls | wc -l はよく見るパターンだけど、ファイル名に改行が含まれていると数が狂う。頑丈にやるなら find の -print0 と wc の --files0-from=- を組み合わせる。

ターミナル
find . -maxdepth 1 -print0 | wc -l --files0-from=-

--files0-from=F はNUL区切りのファイル名リストを読み込むオプションで、find -print0 の出力と相性がいい。

grep -c のほうが速いこともある

ログの中で特定パターンにマッチする行数を数えたいだけなら、grep -c "error" file.log の方が grep "error" file.log | wc -l よりパイプが1本減る分、単純に速い。用途が「マッチ行数」だけならこっちで十分。

空ファイルの扱い

空ファイルは行数・単語数・バイト数すべて0になる。当たり前ではあるけど、スクリプトで0除算とかの判定に使うときは一応押さえておく。