この記事では、RHEL 9系/Ubuntu 24.04 LTSを想定環境として、シェルスクリプト内でLC_ALL、LANG、LC_COLLATEなどのロケール変数を固定すべき理由と設計方法を解説します。スクリプトの動作が環境によってブレる問題を根本から防ぐ「スクリプト内ロケール固定」の設計指針を身につけましょう。
この記事のポイント
・LC_ALL=C をexportするとsort順・日付・エラーメッセージ言語がCロケールに固定される
・優先順位は LC_ALL > LC_COLLATEなど個別LC_* > LANG の順
・日本語を含むデータには「LC_ALL=C sort」のようにコマンド前置で局所固定する
・利用者に見せる表示出力にはロケール固定を適用しない(日本語表示が崩れる)
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜシェルスクリプトでロケールが問題になるのか
シェルスクリプトの動作は、実行時のロケール設定に意外なほど深く影響されます。代表的な問題領域は次の3つです。1. sort順が環境ごとに変わる
日本語環境(LANG=ja_JP.UTF-8)でsortコマンドを実行すると、文字の照合順序がロケールのコレーション規則に従います。英数字と記号が混在するリストでは、Cロケール(バイト順)と日本語ロケール(言語的照合順)でソート結果が異なります。スクリプトがソート済みリストを前提に処理を進める場合、この差は誤動作の直接原因になります。
2. dateコマンドの出力形式が変わる
`date +"%A"` などで曜日名・月名を取得するとき、LANG=ja_JP.UTF-8では日本語名(「土曜日」「1月」)が返ります。スクリプトがdateの出力を文字列比較で条件分岐させている場合、日本語環境では比較が成立しなくなります。
3. エラーメッセージの言語が変わる
コマンドのエラー出力をgrepでキャッチして条件分岐させているスクリプトは、ロケールが変わるとエラーメッセージの言語が変わり、grepが何もマッチしなくなります。たとえば `grep "No such file"` が英語のエラーメッセージを前提にしていると、日本語環境では「そのようなファイルやディレクトリはありません」が返るため、エラーハンドリングがスキップされます。
LC_ALL・LANG・LC_COLLATEの優先順位を押さえる
Linuxのロケール関連の環境変数には明確な優先順位があります。ここを正しく理解しておかないと、設定したつもりなのに効かないという状況に陥ります。| 変数 | 役割 | 優先順位 |
|---|---|---|
LC_ALL |
全ロケールカテゴリを一括上書き | 最高(他の設定をすべて無効化) |
LC_COLLATE |
文字列の照合・sortの順序 | LC_ALLがない場合に有効 |
LC_TIME |
日付・時刻の表示形式 | LC_ALLがない場合に有効 |
LC_MESSAGES |
コマンドのメッセージ言語 | LC_ALLがない場合に有効 |
LANG |
デフォルトのロケール(個別LC_*が未設定の場合のフォールバック) | 最低 |
重要なのは「LANGだけ固定しても効かない場合がある」という点です。利用者の実行環境でLC_ALLやLC_COLLATEが設定されていると、スクリプト内でLANGに設定した値が上書きされます。スクリプト内でLANGだけを固定するよりも、LC_ALLを設定するほうが確実です。
シェルスクリプトでロケールを固定する設計
1. スクリプト冒頭でLC_ALL=Cをexportする
スクリプト全体でsortやdate、grepのエラーメッセージ比較を安定させたい場合は、スクリプトの先頭でLC_ALLをexportします。#!/bin/bash # ロケールをCに固定してスクリプト全体の出力を一定にする export LC_ALL=C export LANG=C # 以降の処理 sort /tmp/input.txt > /tmp/sorted.txt
2. コマンドごとにロケールを前置する方法
スクリプト全体にLC_ALL=Cを適用したくない場合(利用者への表示出力が日本語である必要がある場合など)は、特定のコマンドにだけロケールを前置する書き方を使います。#!/bin/bash # sortだけCロケールで実行する LC_ALL=C sort /tmp/input.txt > /tmp/sorted.txt # dateはCロケールで出力させてスクリプト内でパースする today=$(LC_ALL=C date +"%Y-%m-%d") echo "処理日: ${today}"
LC_ALL=Cで変わる具体的な挙動
sortコマンドの順序変化
日本語環境とCロケールでのsort順の違いを見てみましょう。# 入力ファイル(出力例・環境により異なります) $ cat /tmp/input.txt banana apple Cherry delta # 日本語ロケール(LANG=ja_JP.UTF-8)でのsort $ sort /tmp/input.txt apple banana Cherry delta # LC_ALL=C でのsort $ LC_ALL=C sort /tmp/input.txt Cherry apple banana delta
dateコマンドの出力変化
`+"%Y-%m-%d"` のような書式指定を使う分には問題になりませんが、曜日名・月名を出力させる書式は要注意です。# 日本語ロケールでの曜日出力(出力例・環境により異なります) $ LANG=ja_JP.UTF-8 date +"%A" 土曜日 # Cロケールでの曜日出力(出力例・環境により異なります) $ LC_ALL=C date +"%A" Saturday
エラーメッセージの言語変化
# 日本語ロケールでのエラーメッセージ(出力例・環境により異なります) $ LANG=ja_JP.UTF-8 ls /nonexistent 2>&1 ls: '/nonexistent' にアクセスできません: そのようなファイルやディレクトリはありません # Cロケールでのエラーメッセージ(出力例・環境により異なります) $ LC_ALL=C ls /nonexistent 2>&1 ls: cannot access '/nonexistent': No such file or directory
LC_ALL=Cを使うべきでない場面とC.UTF-8の活用
LC_ALL=Cがすべての状況で最善というわけではありません。特に日本語などのマルチバイト文字を扱う箇所には注意が必要です。LC_ALL=Cでマルチバイト文字の処理が変わる
LC_ALL=Cはロケールをシングルバイトのバイト列として扱います。その結果、次のような副作用が出ます。
・`${#変数}` での文字数カウントがバイト数になる(日本語1文字がUTF-8では3バイトなので、1文字が3とカウントされる)
・`wc -m` がバイト数をカウントする
・sedやgrepの `.`(ドット)や `[a-z]` などの文字クラスが1バイト単位で機能し、マルチバイト文字を意図通りにマッチできない
日本語を含むデータを処理するスクリプトでLC_ALL=Cをスクリプト全体に適用すると、文字列長の計算やマルチバイト文字のパターンマッチングが正しく動作しません。
C.UTF-8の使い方
Cロケールの「バイト順sort・英語エラーメッセージ」という特性を保ちながら、UTF-8のマルチバイト文字を正しく扱いたい場合は `C.UTF-8` を検討します。
# C.UTF-8が使えるか確認する(出力例・環境により異なります) $ locale -a | grep -i "c.utf" C.utf8
推奨設計パターン
日本語を含むデータを扱うスクリプトでは、次のような使い分けが実践的です。
#!/bin/bash # 内部処理(sort・grepのエラー比較)はCロケールで前置 LC_ALL=C sort /tmp/list.txt > /tmp/sorted.txt # 文字列長のカウント(日本語含む場合はLC_ALL=Cを外す) # LC_ALL=C の状態では${#str}がバイト数になるため、文字数が必要なら # LC_ALL=ja_JP.UTF-8(またはC.UTF-8)のコマンドを別途使う # 利用者向けメッセージはLCを変えずに表示 echo "処理が完了しました。"
利用者に見せる表示にはロケール固定を使わない
ログや内部データの整理にはLC_ALL=Cが適していますが、スクリプトが利用者に日本語でメッセージを表示する箇所にはロケール固定を適用しないようにします。スクリプト全体にexport LC_ALL=Cを設定している場合でも、表示用のechoは影響を受けないため、日本語のechoはそのまま使えます(echoコマンド自体はロケールに依存しないため)。
よくあるトラブルと対処法
LANGを設定したのに効かない
利用者の環境でLC_ALLが設定されているとLANGは無視されます。LC_ALLはすべてのLC_*変数を上書きするため、スクリプト内でLANGだけ設定しても利用者側のLC_ALLが優先されます。確実に固定するには `export LC_ALL=C` を使います。# 効かないケース(利用者のLC_ALLが優先される) export LANG=C # 確実に固定するにはLC_ALLを使う export LC_ALL=C
sortの結果がLC_ALL=C設定後も期待と異なる
LC_ALL=Cでsortしたにもかかわらず期待と異なる結果になる場合、入力ファイルの文字コードを確認します。BOM付き(UTF-8 BOM付き)のファイルはCロケールでもBOM文字(EF BB BF)がバイト比較に影響します。`file` コマンドでファイルの文字コードを確認しましょう。# ファイルのBOM確認(出力例・環境により異なります) $ file /tmp/input.txt /tmp/input.txt: UTF-8 Unicode (with BOM) text # xxdでBOM先頭3バイト確認(出力例・環境により異なります) $ xxd /tmp/input.txt | head -1 00000000: efbb bf61 7070 6c65 0a62 616e 616e 610a ...apple.banana.
C.UTF-8がないと言われる
`locale -a` でC.UTF-8が見つからない環境でC.UTF-8を設定しようとすると、`locale: Cannot set LC_ALL to default locale` のようなエラーが出ます。その環境ではC.UTF-8を使わず、日本語を扱う箇所のみLC_ALL=Cを外してコマンドを実行するか、en_US.UTF-8が使えるか確認します。本記事のまとめ
| 設計パターン | 使い方の例 | 適した場面 |
|---|---|---|
| スクリプト冒頭でexport | export LC_ALL=C |
スクリプト全体でsort・date・エラー比較を安定させたいとき |
| コマンド前置 | LC_ALL=C sort /tmp/list.txt |
日本語表示を維持しながら特定コマンドだけ固定したいとき |
| C.UTF-8を使う | export LC_ALL=C.UTF-8 |
バイト順sortを維持しつつUTF-8マルチバイトを正しく扱いたいとき(環境依存・事前確認必要) |
| 固定しない | (適用しない) | 利用者向けの日本語メッセージ表示、対話的な入出力部分 |
ロケール変数の優先順位(LC_ALL > 個別LC_* > LANG)を把握した上で、スクリプトの目的(内部処理か表示か)に合わせて適用範囲を設計することが大切です。特に日本語を含むデータを扱うスクリプトでは、LC_ALL=Cをスクリプト全体に適用せず、コマンド前置でsortやdateにだけ絞ることをおすすめします。
>> シェルスクリプト入門で詳しく見る
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:パイプでつなげるシェルスクリプトの入出力設計|標準入力・標準出力・標準エラーの役割を分けて再利用しやすくする方法
- この記事の属するカテゴリ:シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます