シェルスクリプトのロケール設計|LC_ALL=Cでsort順・日付・数値の出力が環境ごとに変わる問題を防ぐ方法

運営:リナックスマスター.JP(代表 宮崎智広)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > シェルスクリプト > シェルスクリプトのロケール設計|LC_ALL=Cでsort順・日付・数値の出力が環境ごとに変わる問題を防ぐ方法
「サーバーAでは正しく動いていたのに、サーバーBに持っていったらsortの順序がバラバラになった」「同じスクリプトなのに、日本語環境で動かすと日付のパースが失敗する」。こうしたトラブルの原因の多くが、シェルスクリプト内でのロケール設定の抜けです。

この記事では、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つだけ/30秒/解除は3秒 / 詳細はこちら

なぜシェルスクリプトでロケールが問題になるのか

シェルスクリプトの動作は、実行時のロケール設定に意外なほど深く影響されます。代表的な問題領域は次の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行でスクリプトが動作する環境のロケール設定を無視し、バイト順sortや英語メッセージで動作します。exportした変数は子プロセスにも引き継がれます。

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` はそのコマンドの実行時にだけ環境変数を設定し、スクリプトの環境変数には影響を与えません。複数箇所で繰り返すのが煩雑であれば、スクリプト冒頭でexportする手順1の方がシンプルです。

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

Cロケールではバイト順(ASCIIコード順)でソートされるため、大文字(A=65)が小文字(a=97)より先に来ます。日本語ロケールでは言語的な照合規則が適用され、大文字・小文字を区別しない順序になることがあります。スクリプト内でsortの結果を二分探索や比較処理に使う場合、この差は致命的なバグの原因になります。

dateコマンドの出力変化

`+"%Y-%m-%d"` のような書式指定を使う分には問題になりませんが、曜日名・月名を出力させる書式は要注意です。

# 日本語ロケールでの曜日出力(出力例・環境により異なります) $ LANG=ja_JP.UTF-8 date +"%A" 土曜日 # Cロケールでの曜日出力(出力例・環境により異なります) $ LC_ALL=C date +"%A" Saturday

スクリプト内で `%A` や `%B`(月名)を取得して文字列比較で条件分岐させている場合、ロケールが変わると比較が成立しなくなります。スクリプト内部のパース用には `+"%Y-%m-%d"` や `+"%s"` (Unixタイムスタンプ)などロケール非依存の書式を使うか、LC_ALL=C前置を徹底することをおすすめします。

エラーメッセージの言語変化

# 日本語ロケールでのエラーメッセージ(出力例・環境により異なります) $ 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

エラーメッセージを `grep "No such file"` でキャッチしてretryロジックを走らせているスクリプトは、日本語環境ではgrepがマッチせず、エラーハンドリングがスキップされます。こうした処理には `export LC_ALL=C` か、LC_MESSAGES=Cのコマンド前置が必要です。

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

ただし、C.UTF-8はすべてのLinuxディストリビューション・バージョンで利用できるわけではありません。スクリプト冒頭で `locale -a` によりC.UTF-8の存在を確認してから使うか、必要なコマンドにだけ前置する設計を優先します。

推奨設計パターン
日本語を含むデータを扱うスクリプトでは、次のような使い分けが実践的です。

#!/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.

BOMを除去するには `sed -i '1s/^//'` を使います。

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にだけ絞ることをおすすめします。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、シェルスクリプトのロケール設計を含む自動化・エラーハンドリングの実践スキルを体系的に学べる環境を用意しています。

>> シェルスクリプト入門で詳しく見る

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録

運営

リナックスマスター.JP(代表 宮崎智広)

株式会社イーネットマーキュリーが運営するLinux教育サイトです。代表の宮崎智広は現役のLinuxサーバー管理者として20年以上の実務経験を持ち、累計3,100名以上のエンジニアを指導してきました。実践型のLinuxセミナーや無料マニュアルの配布を通じてLinux人材の育成に取り組んでいます。