wc -w の「単語」は、国語辞典や形態素解析の語ではありません。基本的には空白で区切られた、長さが 0 でない文字列の並びを数えます。そのため red,blue は空白がなければ 1、日本語の 今日は晴れです も通常 1 です。英語風の空白区切り文書の概算には適しますが、日本語の語彙数、検索語数、自然言語のトークン数を直接表すものではありません。 確認ポイント:wc -wのwordは空白とlocaleに基づき、日本語の形態素数を数える機能ではありません。
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.txt、3 .../japanese.txt、7 total、stdin=4 です。words.txt の一行目は red、blue、green の3個、二行目の red,blue はカンマの前後に空白がないため1個です。japanese.txt は一行目全体が1個、二行目は空白で東京と大阪に分かれて2個です。標準入力リダイレクトではファイル名が付かないため、数値を変数へ取り込みやすくなります。
タブ、改行、空白類は区切りとして働きますが、どの文字を空白と分類するかは locale や実装の影響を受けます。GNU wc は multibyte locale で追加の Unicode 空白文字を扱い、POSIXLY_CORRECT の有無でも一部の扱いが変わり得ます。同じデータをサーバー間で比較するなら locale と wc --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-8 が locale -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
抽出結果は red、blue、green、tea の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 でそのディレクトリだけを片付ける。

コメント