サーバー上で決められた時刻に特定のスクリプトを走らせたい、という時に役立つのがCron Job。さくらレンタルサーバもその機能を提供しており、コントロールパネルから設定可能。毎日 AWStats に Apache ログをインポートさせるのに使えたり、と便利。ただ、Perl のスクリプトはそのファイルのパスを指定して実行されるが、Bash の場合、気をつけないとハマる。
bash コマンドのパス
さくらサーバーのスタンダードプランでは、Cron Job の登録数は5つまでという制限がある。今回、1つの登録で複数まとめて走らせようと、.sh
のバッシュスクリプトを書いたのだが、実行されないという罠にハマった。
設定ページはコントロールパネルのアプリケーションの設定 -> CRONの設定から開くことができる。Perl スクリプトなどは /home/ユーザー名/hoge/awstats/cgi-bin/awstats.pl -option
などとして実行コマンドのフィールドには、直接コマンド名不要で、スクリプトのファイルのパスを指定きる。しかし、バッシュスクリプトは対象のスクリプトファイルのパスを指定しても「入力内容に問題のある項目があります。」というメッセージがでて受け付けてくれない。これは、コマンド名 パス
という風に、コマンド名を先に渡してその後に、実行ファイルを書いてあげる必要がある。
- ☓
/home/{user name}/scripts/test.sh
- ○
{command} /home/{user name}/scripts/test.sh
ここで、そのコマンド名を単にbash
とすると実行してくれないので注意が必要。
- ☓
bash /home/{user name}/scripts/test.sh
これを sh
にすると、実行はしてくれるのだけれど、色々制約がある。
- ○
sh /home/{user name}/scripts/test.sh
$BASH_SOURCE 変数が使えない罠 (sh の場合)
$BASH_SOURCE
変数は実行中のスクリプトのパスなどを配列として格納してくれている。
BASH_SOURCE
(ソース: FreeBSD Manual Pages Bash)
An array variable whose members are the source filenames corre-
sponding to the elements in the FUNCNAME array variable.
1 2 |
% echo ${BASH_SOURCE[0]} BASH_SOURCE: Undefined variable. |
ただ、この変数、 sh でスクリプトを走らせるとアクセスできないみたい。これを知らずに sh
で走らせると、ハマる。
1行目の記述 #!/bin/bash
ということは、バッシュスクリプト内の1行目の記述も、
1 |
#!/bin/bash |
ではなく、
1 |
#!/bin/sh |
にしなければならないのかとテストしてみたが、これは別に #!/bin/bash
で良いみたい。んー、よくわからん。
Bash で走らせるには
細かな制約が絡んでくるので、sh
でなくて bash
で走らせれるならそれにこしたことはない、ということで、bash
の位置を確認。ついでに sh
, csh
も。
1 2 3 4 5 6 |
% which bash /usr/local/bin/bash % which sh /bin/sh % which csh /bin/csh |
/usr/local/bin/bash
ということなので、これを Cron のコマンドとして渡してあげる。
- ◎
/usr/local/bin/bash /home/{user name}/scripts/test.sh
これで、スクリプトが bash
で呼び出されるようになった。
Pathが違う罠
他に注意点として、Cron で実行される際の環境変数の値が、各ユーザーがコンソールにログインして走らせる際のものと違う。スクリプト内で$PATH
を参照している場合、想定しているディレクトリとは違う場所が入っていたりする。
これに関しては、もしスクリプト内で何かプログラムを走らせる場合、パスを再度指定してあげると良い。次のラインをスクリプトの先頭部分 #!/bin/sh
や #!/bin/bash
の下に記述してあげる。
1 |
PATH="$PATH":/usr/local/sbin:/usr/local/bin |
date コマンドのオプションの仕様が違う罠
date
コマンドは現在時刻や何日前といった時間の計算に便利なのだけど、他のOSのdate
コマンドのオプションのシンタックスが違うため、そのフォーマットに則った記述でないとエラーを引き起こし、スクリプトがきちんと動作しなくなる。
具体的には、昨日の日付を取得しようとして、
1 |
date -d "yesterday 13:00" '+%Y-%m-%d' |
としても、FreeBSD では通らない。
1 2 3 |
% date -d "yesterday 13:00" '+%Y-%m-%d' usage: date [-jnu] [-d dst] [-r seconds] [-t west] [-v[+|-]val[ymwdHMS]] ... [-f fmt date | [[[[[cc]yy]mm]dd]HH]MM[.ss]] [+format] |
1 |
echo `date -v-1d +%F` |
としなければならない。これを知らないとハマる。というかハマった。
簡単なテスト方法
Cron が正常に走ってるかどうか確かめる方法として、現在時刻の1分後の分を実行日時の分のフィールドに与える。で、スクリプトにログを記録する記述を入れて、正常にログが生成されるかどうかをチェックする。
ちなみにサーバーの現在の時刻を知りたい場合、コンソールで以下のように打つと出る。これで時刻を確認して過ぎていれば、ログがあるかどうか見るという作業の流れを作れる。
1 2 |
% date +"%H:%M:%S" 13:20:36 |
$HOME/scripts/test.sh
({user-name} はさくらインターネットから与えられたアカウント名)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#!/bin/bash LOG_DIR_PATH=/home/{user name}/scripts/log mkdir -p $LOG_DIR_PATH SCRIPT_DIR_PATH="$( cd "$(dirname "$0")" ; pwd -P )" echo TEST: Testing >> $LOG_DIR_PATH/test.log echo HOME: $HOME >> $LOG_DIR_PATH/test.log echo PATH: $PATH >> $LOG_DIR_PATH/test.log echo USER: $USER >> $LOG_DIR_PATH/test.log echo PWD: $PWD >> $LOG_DIR_PATH/test.log echo SCRIPT_DIR_PATH: $SCRIPT_DIR_PATH >> $LOG_DIR_PATH/test.log SOURCE="${BASH_SOURCE[0]}" echo SOURCE: $SOURCE >> $LOG_DIR_PATH/test.log |
こんな感じのテストスクリプトを走らせる。結果、対象スクリプトと同階層に log
ディレクトリが作られ、その中に、test.log
が生成されればOK。
test.log
1 2 3 4 5 |
TEST: Testing HOME: /home/{user name} PATH: /usr/bin:/bin USER: {user name} PWD: /home/{user name} SCRIPT_DIR_PATH: /home/{user name}/scripts SOURCE: /home/{user name}/scripts/test.sh |
$PATH
が /usr/local/bin/
を含んでいないので、bash
が通らないわけだ。ここで sh
で走らせてたりすると、SOURCE
に値が入ってなかったりする。
デイリースクリプト
これで、バッシュスクリプトをクロンジョブで走らせることができたので、デイリーで走らせるスクリプトを書く。
次のスクリプトは daily
ディレクトリに入っている Bash スクリプトを順に実行していくというもの。実行したスクリプトは時刻とともに、そのパスを log
内のログファイルに記録していく。これで何がいつ走ったかは把握できる。ログファイルは月ごとのに名称を変えるので、かさばる心配もしなくて良い。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#!/bin/bash # For cron, the path needs to be set. PATH="$PATH":/usr/local/sbin:/usr/local/bin SCRIPT_DIR_PATH="$( cd "$(dirname "$0")" ; pwd -P )" # Ensure the log and daily directories exist DIR_PATH_LOG=$SCRIPT_DIR_PATH/log DIR_PATH_DAILY=$SCRIPT_DIR_PATH/daily mkdir -p $DIR_PATH_LOG mkdir -p $DIR_PATH_DAILY ThisMonth=$(date +"%Y%m") for FILE_PATH in $DIR_PATH_DAILY/*.sh; do if [ ! -f "$FILE_PATH" ]; then # this line is reached if no file matches continue fi echo $FILE_PATH echo $(date +"%Y%m%d %H%M%S") $FILE_PATH >> $DIR_PATH_LOG/daily_$ThisMonth.log bash $FILE_PATH done |
で、 daily
ディレクトリに Bash スクリプトを放り込んでいくだけ。後は、コントロールパネルで、時刻と頻度を希望のものすれば設定完了。
共用サーバの呪縛
見てきたとおり、さくらレンタルサーバの Cron で Bash スクリプトを走らせようとすると、数々の罠が張り巡らされており、やりたいことを達成するのは至難の技ということがわかった。これはまだまだ序の口で、何かちょっとしたことをしようとするだけで、数え切れない壁が立ちはだかる。共用のレンタルサーバーというのは、一見、誰でも運用できそうなイメージがあり、敷居が低いように見える。しかし、自由度が低く、その縛りをかいくぐるスキルが問われる為、実はめちゃくちゃハードルが高い。