Linuxでテキストファイルの単語数をカウントする方法

wc -w の「単語」は、国語辞典や形態素解析の語ではありません。基本的には空白で区切られた、長さが 0 でない文字列の並びを数えます。そのため red,blue は空白がなければ 1、日本語の 今日は晴れです も通常 1 です。英語風の空白区切り文書の概算には適しますが、日本語の語彙数、検索語数、自然言語のトークン数を直接表すものではありません。 確認ポイント:wc -wのwordは空白とlocaleに基づき、日本語の形態素数を数える機能ではありません。

日程Fit。無料・登録不要。「いつ空いてる?」を、ひとつのリンクで。リンクを送って、○△×でかんたん日程調整。無料で日程を作る。
目次

wc -wの区切り規則をASCIIと日本語で比較する

work=$(mktemp -d) || exit 1
trap 'rm -r -- "$work"' EXIT
printf 'red  blue\tgreen\nred,blue\n' >"$work/words.txt"
printf '今日は晴れです\n東京 大阪\n' >"$work/japanese.txt"
wc -w -- "$work/words.txt" "$work/japanese.txt"
printf 'stdin=%s\n' "$(wc -w <"$work/words.txt")"

結果は 4 .../words.txt3 .../japanese.txt7 totalstdin=4 です。words.txt の一行目は red、blue、green の3個、二行目の red,blue はカンマの前後に空白がないため1個です。japanese.txt は一行目全体が1個、二行目は空白で東京と大阪に分かれて2個です。標準入力リダイレクトではファイル名が付かないため、数値を変数へ取り込みやすくなります。

タブ、改行、空白類は区切りとして働きますが、どの文字を空白と分類するかは locale や実装の影響を受けます。GNU wc は multibyte locale で追加の Unicode 空白文字を扱い、POSIXLY_CORRECT の有無でも一部の扱いが変わり得ます。同じデータをサーバー間で比較するなら localewc --version を記録し、ASCII 区切りだけを前提にできる処理では LC_ALL=C wc -w と locale を固定します。ただし C locale にしても日本語を言語学的に分割できるわけではありません。

行・単語・文字・バイトを同時に読む

wc-l が改行数、-w が空白区切り語数、-m が文字数、-c がバイト数です。文字数とバイト数は UTF-8 の非 ASCII 文字で異なります。複数オプションを同時指定した出力順は、行、単語、文字、バイト、最大行長の標準順で、指定順そのままとは限りません。スクリプトで一値だけ必要なら一オプションずつ使います。

printf 'red blue\n' | wc -l -w -m -c
printf '日本語\n' | LC_ALL=C.UTF-8 wc -l -w -m -c

GNU/Linux に C.UTF-8 locale があれば、正確な数値は最初が 1 2 9 9、次が 1 1 4 10 です。日本語3文字と改行で4文字、UTF-8 では日本語が各3バイトなので改行を含め10バイトです。C.UTF-8locale -a に無い環境では、利用可能な UTF-8 locale(例: ja_JP.UTF-8)を明示します。不正なバイト列があると -m の結果や診断が期待と異なるため、バイトサイズが要件なら -c、文字数なら encoding 検証を先に行います。

句読点で区切りたいときだけ grep -o を使う

grep -o は一致した部分だけを一行ずつ出します。英字の連続を「トークン」と定義できる ASCII データなら、正規表現で抽出してから数えられます。これは wc -w の代替というより、別の単語定義を明示した処理です。

printf '%s\n' 'red,blue 42 green-tea' >"$work/tokens.txt"
LC_ALL=C grep -oE '[A-Za-z]+' "$work/tokens.txt"
printf 'grep_rc=%d\n' "$?"
LC_ALL=C grep -oE '[A-Za-z]+' "$work/tokens.txt" | wc -l

抽出結果は redbluegreentea の4行で、最後の件数は 4 です。数字 42 は正規表現に含めていないため数えません。[[:alpha:]]+ は locale の文字分類を使うので Unicode 文字を拾う場合がありますが、それでも日本語の語境界を決定する規則にはなりません。要件が「ASCII 識別子」なら [A-Za-z_][A-Za-z0-9_]* など、対象の文法を正規表現として定義します。

一致がないと GNU grep は終了値 1 ですが、後段の wc -l は 0 で成功します。Bash の既定ではパイプ全体が 0 になり、検索失敗と「0件」を区別できません。0件が正常なら matches=$(grep -oE ... || [[ $? == 1 ]]) のように 1 だけを許容し、2(入出力や正規表現のエラー)は失敗として扱います。set -o pipefail を使う場合も、grep の 1 を業務上の正常な 0件とするか先に決めます。

日本語は形態素解析器の辞書込みで数える

日本語の「今日は晴れです」を 今日・は・晴れ・です のように分けたい場合、空白だけを見る wc や文字クラス正規表現では不十分です。MeCab、Sudachi など、組織で採用した形態素解析器と辞書を先に選び、その出力トークンを数えます。解析器名だけでなくバージョン、辞書名、正規化、未知語・記号を数える規則を保存しないと、同じ文章でも件数が変わります。

command -v mecab >/dev/null || { printf 'mecab is not installed\n' >&2; exit 2; }
mecab --version
printf '今日は晴れです\n' | mecab -Owakati
printf '今日は晴れです\n' | mecab -Owakati | wc -w

特定の MeCab と辞書で分かち書きが 今日 は 晴れ です なら件数は 4 です。ただし「必ず4」とはせず、実際の -Owakati 出力を検査して期待辞書を固定します。解析器が未導入なら例はメッセージを標準エラーへ出して終了値 2 で止まり、wc -w の 1 を日本語単語数として代用しません。大量処理では一件ずつプロセスを起動せず、解析器が提供するバッチ入力方法を使います。

ファイル名と複数ファイルを安全に扱う

空白を含むファイル名は wc -w -- "$file" と引用します。glob の *.txt は一致しない場合に文字列のまま残るシェルがあるので、再帰検索では find から引数配列として直接渡します。

mkdir "$work/docs"
printf 'one two\n' >"$work/docs/report one.txt"
printf 'three\n' >"$work/docs/-draft.txt"
find "$work/docs" -type f -name '*.txt' -exec wc -w -- {} +

出力順は保証されませんが、対応する件数は report one.txt が2、-draft.txt が1、合計が3です。検索起点が付くので -draft.txt も通常は .../docs/-draft.txt という引数になり、さらに -- でオプション解釈を止めています。ファイル名に改行があっても -exec ... {} + は一つの引数として渡します。ファイル数が多いと wc が複数回起動して total が複数出る点は、総計を機械処理する際に注意します。

使い分けと検証

  • 空白区切り項目数が要件: wc -w。末尾の句読点も同じ項目に含まれる。
  • 仕様化した ASCII/locale 文字列の出現数: grep -o で抽出し、grep の終了値 1 と 2 を区別する。
  • 日本語の語・形態素数: 解析器、辞書、正規化、記号規則を固定してからトークン出力を数える。
  • 文字数: UTF-8 locale を確認して wc -m。保存容量: wc -c。改行数: wc -l
  • 読み取りだけなら原本変更はない。検証用ファイルは mktemp 配下に限定し、trap でそのディレクトリだけを片付ける。

公式情報・参考資料

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次