
先日、業務で実装した機能に「特定条件でだけ発生する不具合」が見つかりました。
問題の箇所は、docker run でコンテナ内の特定パスがディレクトリかどうかを判定する処理です。 stat -c '%F' の返り値が directory ならディレクトリ、と文字列比較で判断していました。
何が起きたか
日本語の言語ファイルが入ったイメージ上では、stat -c '%F' の結果が directory ではなく ディレクトリ になりました。
その結果、実装側の判定にヒットせず、ディレクトリをファイルだと誤判定してしまう不具合が発生しました。
ここでの本質は Docker 固有の罠ではなく、ロケール依存の文字列を判定に使っていたことです。 今回は Docker コンテナ内で発生しましたが、同じ設計ならホスト環境でも起こりえます。
再現イメージとしては、次のような状態です。
# 想定していた返り値
$ stat -c '%F' /target/path
"directory"
# 実際に返ってきたケース
$ stat -c '%F' /target/path
"ディレクトリ"当時の対応と、その後の見直し
当時の私は、コード変更による影響範囲を最小限にしたかったため LANG を固定する形で対応しました。
実装は TypeScript + execa で書いていたため、コマンド実行時の環境変数に LANG=C を渡す形です。
この対応で不具合は解消できましたが、後から調べると「より推奨されるのは、文字列比較ではなく終了コードで判定すること」でした。
これは stat の個別仕様というより、人間向け出力に依存せず終了コードを判定に使うという UNIX の設計原則に沿った考え方です。
例えば、ディレクトリ判定自体は test -d を使えばロケール非依存で扱えます。
docker run --rm image sh -lc 'test -d /target/path'
# 終了コード 0: ディレクトリ
# 終了コード 1: ディレクトリではない今回の教訓
今回の学びはシンプルです。
プロダクション採用するコマンド・オプションは、挙動(特にロケール依存性)まで含めて先に調べるべき。
(この業界に入って結構経つのに、今さらこの学びを得るのもかなり恥ずかしいのですが…)
「普段の手元環境でたまたま期待どおりに動く」だけだと、実行環境が変わった瞬間に壊れる可能性があります。
実装時のチェックポイントとして、最低でも次は確認しておくべきだと感じました。
- 返り値がロケール依存かどうか
- 文字列比較より終了コード判定に寄せられないか
まとめ
今回は stat -c '%F' の返り値を固定文字列で判定していたことが、ロケール差分で不具合化しました。
実務上は LANG 固定で急場をしのげる場面もありますが、恒久的には終了コードベースの判定へ寄せる方が安全です。
小さな処理ほど「仕様を知っているつもり」になりやすいので、今後はコマンド1つでも仕様確認を怠らないようにします。