$ cat randomsleep.sh #!/bin/bash NUM=`expr $RANDOM % 3600` echo $NUM sleep $NUM $ sh randomsleep.sh; wget http://www.google.com -O -
randomsheep.shで0-3599秒の適当な時間だけsleepする。其の後に適当なプログラムを呼び出す。このくらいならばシェルスクリプトにしなくても良いのかもしれない。
$ sleep `expr $RANDOM % 3600`; wget http://www.google.com -O -
ということで、これをcrontabに登録すれば「1時間に必ず1回だけ呼び出されるけれど、呼び出されるタイミングは呼び出されるたびに違う」という挙動を実現できる。登録のときは%をエスケープする(\%にする)こと。
$ crontab -e 0 * * * * sleep `expr $RANDOM \% 3600`; wget http://www.google.com -O -
- jijixi's diary - 書くネタが無い , wget で結果を標準出力に吐く
- シェルスクリプトでランダムな数字を得る方法 - World Wide Walker
- cron 登録ジョブ crontab のバックアップ
- crontabでは%をエスケープしなければならないなんて・・・ - はてな?のぐうたら玉子丼
- crontab - Wikipedia
この方法の問題はランダムな時間だけ、ジョブを呼び出したシェルとsleepが常駐してしまうことだ。これによってメモリが消費されてしまうので、ぎりぎりのメモリで頑張っているマシンでは辛い方法だ。例えば、psでジョブのプロセスIDをチェックして、topのバッチモード一回でメモリ使用量を確認すると下のようになる。
$ ps waxf $ top -s -p 16929 -p 16932 -b -n1 top - 17:27:24 up 2 days, 20:51, 1 user, load average: 0.29, 0.22, 0.19 Tasks: 2 total, 0 running, 2 sleeping, 0 stopped, 0 zombie Cpu(s): 16.8%us, 22.2%sy, 0.1%ni, 50.6%id, 8.4%wa, 0.4%hi, 1.5%si, 0.0%st Mem: 125988k total, 123352k used, 2636k free, 768k buffers Swap: 369452k total, 57296k used, 312156k free, 38544k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 16929 hoge 18 0 4128 1420 1000 S 0.0 1.1 0:00.01 sh 16932 hoge 19 0 2744 468 408 S 0.0 0.4 0:00.00 sleep
topのVIRTで確認するとうちのコンピュータでは、ジョブが終了するまでの間、呼び出しシェルが4.1MBytes、sleepが2.7MBytesほど仮想メモリを占有してsleep状態になってしまう。1時間に一回という微妙な粒度でwgetを呼び出しているので、最長で1時間、最短で0秒間、平均で30分間、つまりこのジョブはメモリ消費の観点からすれば「6.8MBytesの仮想メモリを占有するプロセスが1日の内12時間走る」ことと同義である。これは無駄といえば無駄だな。
さて、上のジョブを実現させたcronジョブにおける、一番の無駄な点はプロセスがメモリを占有した状態でsleep状態になってしまい、其の平均が30分間である点だ。crontabを自動的に書き換えるスクリプトを書いて、この無駄を排してみよう。
cronジョブは最短で1分の粒度で走らせることが出来る。そのため、1日1回当日24時間分のジョブをcrontabに追加出来れば、sleep状態になっている時間の平均を30秒に減らすことが出来、1日平均で24回*30秒=12分にsleep時間を減らせる。そこで、1日に1回0時0分にcrontabを書き換え、1時間に1回目的のジョブをこなすエントリーを追加し、過去に行ったエントリーを削除するシェルスクリプト(pushjob.sh)を書いた。
#!/bin/bash COMMAND=$* MIN=`date '+%M'` HOUR=`date '+%M'` DAY=`date '+%e'` MONTH=`date '+%b'` WEEK=`date '+%a'` TMP_IN=`mktemp -q /tmp/$0.XXXXXX` if [ $? -ne 0 ]; then echo "$0: Can't create temp file, exiting..." exit 1 fi TMP_OUT=`mktemp -q /tmp/$0.XXXXXX` if [ $? -ne 0 ]; then echo "$0: Can't create temp file, exiting..." exit 1 fi CRON='' #crontab -l crontab -l > $TMP_IN grep -v --regex "^[0-9]\{1,2\} [0-9]\{1,2\} [0-9]\{1,2\} [A-Z][a-z]\{2\} [A-Z][a-z]\{2\} sleep [0-9]\{1,2\}; $COMMAND;$" $TMP_IN>$TMP_OUT for i in `seq 0 23` do HOUR=$i MIN=`expr $RANDOM % 60` SEC=`expr $RANDOM % 60` CRON="$MIN $HOUR $DAY $MONTH $WEEK sleep $SEC; $COMMAND;" echo $CRON >> $TMP_OUT done crontab $TMP_OUT rm $TMP_IN $TMP_OUT $crontab -l exit
実際にランダムに実行されるコマンドはpushjob.shの引数となるので今までのジョブでsleep `expr $RANDOM \% 3600`;としていた部分をsh pushjob.shに書き換え 、これを毎日0時0分に走るようにcrontabに追加
$ crontab -e #0 * * * * sleep `expr $RANDOM \% 3600`; wget http://www.google.com -O - 0 0 * * * sh pushjob.sh wget http://www.google.com -O -
これで毎日0時0分にcrontabが下のような内容のジョブが追加され、過去に行ったジョブは消去される。単純にsleepを挟むよりも無駄なメモリ占有を抑えてジョブが走るはずだ。
9 0 13 Feb Fri sleep 40; wget http://www.google.com -O - 18 1 13 Feb Fri sleep 48; wget http://www.google.com -O - 27 2 13 Feb Fri sleep 57; wget http://www.google.com -O - 35 3 13 Feb Fri sleep 5; wget http://www.google.com -O - 44 4 13 Feb Fri sleep 14; wget http://www.google.com -O - 52 5 13 Feb Fri sleep 23; wget http://www.google.com -O - 1 6 13 Feb Fri sleep 31; wget http://www.google.com -O - 2 7 13 Feb Fri sleep 40; wget http://www.google.com -O - 10 8 13 Feb Fri sleep 49; wget http://www.google.com -O - 19 9 13 Feb Fri sleep 57; wget http://www.google.com -O - 27 10 13 Feb Fri sleep 6; wget http://www.google.com -O - 36 11 13 Feb Fri sleep 14; wget http://www.google.com -O - 23 12 13 Feb Fri sleep 53; wget http://www.google.com -O - 32 13 13 Feb Fri sleep 2; wget http://www.google.com -O - 40 14 13 Feb Fri sleep 11; wget http://www.google.com -O - 49 15 13 Feb Fri sleep 19; wget http://www.google.com -O - 57 16 13 Feb Fri sleep 28; wget http://www.google.com -O - 6 17 13 Feb Fri sleep 36; wget http://www.google.com -O - 15 18 13 Feb Fri sleep 45; wget http://www.google.com -O - 23 19 13 Feb Fri sleep 54; wget http://www.google.com -O - 32 20 13 Feb Fri sleep 2; wget http://www.google.com -O - 41 21 13 Feb Fri sleep 11; wget http://www.google.com -O - 49 22 13 Feb Fri sleep 19; wget http://www.google.com -O - 58 23 13 Feb Fri sleep 28; wget http://www.google.com -O -
前にも述べたようにpushjob.shの引数に与えたコマンドを追記するので、下のように全体を''で括ればランダムに行われるジョブの結果を捨てる様にcrontabに追加することもことも出来る。
$ crontab -e 0 0 * * * sh pushjob.sh 'wget http://www.google.com -O - 2>&1 > /dev/null'
これにより追加されるジョブは下のような感じ
9 0 13 Feb Fri sleep 40; wget http://www.google.com -O - 2>&1 > /dev/null; 18 1 13 Feb Fri sleep 48; wget http://www.google.com -O - 2>&1 > /dev/null; 27 2 13 Feb Fri sleep 57; wget http://www.google.com -O - 2>&1 > /dev/null; 35 3 13 Feb Fri sleep 5; wget http://www.google.com -O - 2>&1 > /dev/null; 44 4 13 Feb Fri sleep 14; wget http://www.google.com -O - 2>&1 > /dev/null; 52 5 13 Feb Fri sleep 23; wget http://www.google.com -O - 2>&1 > /dev/null; 1 6 13 Feb Fri sleep 31; wget http://www.google.com -O - 2>&1 > /dev/null; 2 7 13 Feb Fri sleep 40; wget http://www.google.com -O - 2>&1 > /dev/null; 10 8 13 Feb Fri sleep 49; wget http://www.google.com -O - 2>&1 > /dev/null; 19 9 13 Feb Fri sleep 57; wget http://www.google.com -O - 2>&1 > /dev/null; 27 10 13 Feb Fri sleep 6; wget http://www.google.com -O - 2>&1 > /dev/null; 36 11 13 Feb Fri sleep 14; wget http://www.google.com -O - 2>&1 > /dev/null; 23 12 13 Feb Fri sleep 53; wget http://www.google.com -O - 2>&1 > /dev/null; 32 13 13 Feb Fri sleep 2; wget http://www.google.com -O - 2>&1 > /dev/null; 40 14 13 Feb Fri sleep 11; wget http://www.google.com -O - 2>&1 > /dev/null; 49 15 13 Feb Fri sleep 19; wget http://www.google.com -O - 2>&1 > /dev/null; 57 16 13 Feb Fri sleep 28; wget http://www.google.com -O - 2>&1 > /dev/null; 6 17 13 Feb Fri sleep 36; wget http://www.google.com -O - 2>&1 > /dev/null; 15 18 13 Feb Fri sleep 45; wget http://www.google.com -O - 2>&1 > /dev/null; 23 19 13 Feb Fri sleep 54; wget http://www.google.com -O - 2>&1 > /dev/null; 32 20 13 Feb Fri sleep 2; wget http://www.google.com -O - 2>&1 > /dev/null; 41 21 13 Feb Fri sleep 11; wget http://www.google.com -O - 2>&1 > /dev/null; 49 22 13 Feb Fri sleep 19; wget http://www.google.com -O - 2>&1 > /dev/null; 58 23 13 Feb Fri sleep 28; wget http://www.google.com -O - 2>&1 > /dev/null;
この方法の問題点は、毎日0時0分crontabを書き換えるジョブがcronによって走らされてしまうため、このタイミングでcrontabを編集していると内容が変わってしまう可能性があることだ。
- シェルスクリプト 引数 - Google 検索
- bashで始めるシェルスクリプト基礎の基礎(1/2)
- 競合状態を避ける
- 【 mktemp 】 適当なファイル名の空ファイルを作成する:ITpro
- シェルスクリプト テンポラリ - Google 検索
- Manpage of CRONTAB
- crontab - Google 検索
- 【 crontab 】 プログラムを定期的に実行するcrondの設定ファイルを編集する:ITpro
- crontab 表示 - Google 検索
- 日付を取得する - UNIX & Linux コマンド・シェルスクリプト リファレンス
- linux date フォーマット - Google 検索
- Regular Expressions in grep
- grep regex - Google 検索
- シェルスクリプト入門 [制御構文]
- シェルスクリプト for 文 - Google 検索
- ユーザ crontab - Google 検
さて、同じ事を行うためにatは秒単位でもいけそうだという話を聞いて、crontabににatジョブを追加するような方向性も考えたのだが、余分にatdが走るし、atは5分毎にatrun(8)を呼び出すcrondという話もあるし、やめておいた。まぁ実際秒単位指定の出来るat入れるの面倒そうだったのでやめちゃっただけだけど。