Locale

今回はcrontab越しにメールを送信するとメール本文が添付される原因についてまとめた.

概要


サーバー上でスクリプトの挙動確認をする際,コマンドライン上での実行とcrontab越しでの実行で挙動が変わる現象を経験することがあると思う. その原因の一つに,crontab越しの実行時は環境変数が読み込まれないことが挙げられる. それによりたとえば今回紹介する,メール送信時に本文が表示される代わりに添付されてしまう,といった現象が起きる. ロケールを正しく設定すべしというのが今回の内容である.

環境


  • CentOS7

crontab越しの実行の注意点


せっかく作成したシェルスクリプト.コマンドライン上で実行すると問題ないのにcrontab越しに実行すると動かなかったり挙動がおかしかったりといった経験はよくあると思う. その原因は大体において環境変数にある.

crontab実行時には実行ユーザーのプロファイル(.bash_profileなど)は読み込まれない.

# crontabで実行したenvの結果

XDG_SESSION_ID=12456
SHELL=/bin/sh
USER=human
PATH=/usr/bin:/bin
_=/usr/bin/env
PWD=/home/human
LANG=C
HOME=/home/human
SHLVL=2
LOGNAME=human
XDG_RUNTIME_DIR=/run/user/1000

プロファイルを読み込ませるには,たとえばbash -lとする.

#!/bin/bash -l
...

常にbash -lを設定すれば万事オーケーかというとそうでもない. 網羅できないものがある.そう,ロケールである.

現象:添付される本文

メール送信のシェルスクリプトをサーバー上のコマンドラインから実行した場合には問題ないが crontab越しに実行するとメール本文がなく,本文は代わりに添付される現象が見られた.

コマンドラインでメールを読もうとすると以下のようなメッセージ.

application/octet-stream is unsupported

調べたところ改行コードが混在していたりコントロールコードが入っている可能性があるとのこと.

しかしながら本文のファイルを確認したが特に文字コード,改行コードは問題なかった.

$ nkf --guess hoge.txt
UTF-8 (LF)
  • コマンドライン上での実行は問題ない
  • crontab越しの実行で問題あり.何やら原因は文字コード周り
  • ファイル自体の文字コード,改行コードは問題なし

以上から,crontab実行時の文字コード周りが怪しいということでロケールを確認した.

ロケールを設定して解決

ロケールを確認すると以下のようにコマンドライン上とcrontab実行時で値が異なることが分かった.

# crontab越し

# crontab内
* * * * * locale > /tmp/hoge.log

# crontab越しの実行結果
[human@enjoy.stay.home]$ cat /tmp/hoge.log
LANG=C
LC_CTYPE="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_COLLATE="C"
LC_MONETARY="C"
LC_MESSAGES="C"
LC_PAPER="C"
LC_NAME="C"
LC_ADDRESS="C"
LC_TELEPHONE="C"
LC_MEASUREMENT="C"
LC_IDENTIFICATION="C"
LC_ALL=

# コマンドライン越し
[human@enjoy.stay.home]$ locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC=ja_JP.UTF-8
LC_TIME=ja_JP.UTF-8
LC_COLLATE="en_US.UTF-8"
LC_MONETARY=ja_JP.UTF-8
LC_MESSAGES="en_US.UTF-8"
LC_PAPER=ja_JP.UTF-8
LC_NAME=ja_JP.UTF-8
LC_ADDRESS=ja_JP.UTF-8
LC_TELEPHONE=ja_JP.UTF-8
LC_MEASUREMENT=ja_JP.UTF-8
LC_IDENTIFICATION=ja_JP.UTF-8
LC_ALL=

なおロケールはbash -lでcrontab越しに実行しても変更されなかった(LANG=Cのままであった)ためスクリプト内で明示的に設定することで解決した.

# e.g.
export LANG=ja_JP.UTF-8
...

その他

環境変数LANGの値を変更すると,即座にロケール周りの環境変数の値は変更される.

[human@enjoy.stay.home]$ locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC=ja_JP.UTF-8
LC_TIME=ja_JP.UTF-8
LC_COLLATE="en_US.UTF-8"
LC_MONETARY=ja_JP.UTF-8
LC_MESSAGES="en_US.UTF-8"
LC_PAPER=ja_JP.UTF-8
LC_NAME=ja_JP.UTF-8
LC_ADDRESS=ja_JP.UTF-8
LC_TELEPHONE=ja_JP.UTF-8
LC_MEASUREMENT=ja_JP.UTF-8
LC_IDENTIFICATION=ja_JP.UTF-8
LC_ALL=

# LANGの値を変更
[human@enjoy.stay.home]$ export LANG=ja_JP.UTF-8
[human@enjoy.stay.home]$ locale
LANG=ja_JP.UTF-8
LC_CTYPE="ja_JP.UTF-8"
LC_NUMERIC=ja_JP.UTF-8
LC_TIME=ja_JP.UTF-8
LC_COLLATE="ja_JP.UTF-8"
LC_MONETARY=ja_JP.UTF-8
LC_MESSAGES="ja_JP.UTF-8"
LC_PAPER=ja_JP.UTF-8
LC_NAME=ja_JP.UTF-8
LC_ADDRESS=ja_JP.UTF-8
LC_TELEPHONE=ja_JP.UTF-8
LC_MEASUREMENT=ja_JP.UTF-8
LC_IDENTIFICATION=ja_JP.UTF-8
LC_ALL=

またCentOS7ではロケールはクライアントの値が引き継がれることにも留意する.

デフォルトでは、RHEL7 システムは、ssh クライアントからロケール環境変数を受け入れるようになっています。以下のロケール環境変数を ssh クライアントが渡している場合には、ログインスクリプトが実行されるよりも前に設定されます。

sshでログインする際にクライアント側で設定されていたロケールが使用されるのである.

$ echo $LANG
en_US.UTF-8

$ locale
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC=ja_JP.UTF-8
LC_TIME=ja_JP.UTF-8
LC_COLLATE="en_US.UTF-8"
LC_MONETARY=ja_JP.UTF-8
LC_MESSAGES="en_US.UTF-8"
LC_PAPER=ja_JP.UTF-8
LC_NAME=ja_JP.UTF-8
LC_ADDRESS=ja_JP.UTF-8
LC_TELEPHONE=ja_JP.UTF-8
LC_MEASUREMENT=ja_JP.UTF-8
LC_IDENTIFICATION=ja_JP.UTF-8
LC_ALL=


$ ssh enjoy.stay.home -l human

[human@enjoy.stay.home]$ locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC=ja_JP.UTF-8
LC_TIME=ja_JP.UTF-8
LC_COLLATE="en_US.UTF-8"
LC_MONETARY=ja_JP.UTF-8
LC_MESSAGES="en_US.UTF-8"
LC_PAPER=ja_JP.UTF-8
LC_NAME=ja_JP.UTF-8
LC_ADDRESS=ja_JP.UTF-8
LC_TELEPHONE=ja_JP.UTF-8
LC_MEASUREMENT=ja_JP.UTF-8
LC_IDENTIFICATION=ja_JP.UTF-8
LC_ALL=

# --> クライアント環境と同じ値が設定されている


[human@enjoy.stay.home]$ logout

# クライアント環境のロケールを変更
$ export LANG=ja_JP.UTF-8

$ locale
LANG=ja_JP.UTF-8
LANGUAGE=
LC_CTYPE="ja_JP.UTF-8"
LC_NUMERIC=ja_JP.UTF-8
LC_TIME=ja_JP.UTF-8
LC_COLLATE="ja_JP.UTF-8"
LC_MONETARY=ja_JP.UTF-8
LC_MESSAGES="ja_JP.UTF-8"
LC_PAPER=ja_JP.UTF-8
LC_NAME=ja_JP.UTF-8
LC_ADDRESS=ja_JP.UTF-8
LC_TELEPHONE=ja_JP.UTF-8
LC_MEASUREMENT=ja_JP.UTF-8
LC_IDENTIFICATION=ja_JP.UTF-8
LC_ALL=


# この状態でサーバーにログイン
$ ssh enjoy.stay.home -l human

[human@enjoy.stay.home]$ locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC=ja_JP.UTF-8
LC_TIME=ja_JP.UTF-8
LC_COLLATE="en_US.UTF-8"
LC_MONETARY=ja_JP.UTF-8
LC_MESSAGES="en_US.UTF-8"
LC_PAPER=ja_JP.UTF-8
LC_NAME=ja_JP.UTF-8
LC_ADDRESS=ja_JP.UTF-8
LC_TELEPHONE=ja_JP.UTF-8
LC_MEASUREMENT=ja_JP.UTF-8
LC_IDENTIFICATION=ja_JP.UTF-8
LC_ALL=

# --> クライアント環境と同じ値が設定されている

最後に


crontab越しの実行では環境変数が引き継がれない点,またロケール周りはbash -lでも解決されない点を留めておくべし.

参考URL