tomyamaのブログ

日記・雑記。

cronデーモンの実行予定を見易く表示するスクリプト

昔に書いたPerlスクリプトです。PATHの通っている /usr/local/bin/ に置いてコマンドとして使用しています。

 

cron daemonのスケジューリングを確認したい時に使います。

 

cronデーモンとは?

Unix系のOSには、cronというデーモン(サービス)があります。時間とコマンドを指定しておくと、その時間にコマンドを実行してくれる仕組みになっています。Windowsだと「タスク スケジューラ」に該当するものだと思います。が、cron daemonのトリガーは時間だけなので、「タスク スケジューラ」よりももっとシンプルです。

 

cronデーモンに仕事を依頼しておく方法は?

テキストファイルに記述します。Linuxだとcrontab -eで編集できるのですが、他システムがどうだったかはうろ覚えです。man crontabで調べればすぐに判る事なので、覚える必要無しです。

書き方はというと、例えば「毎週土曜日の5時にバックアップを実行」と設定したい場合には以下のような感じで記述します。

 

[crontab]の記述例

# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
0  5  *  *  6  /home/user1/job/differential_backup.sh

 

何かしらサーバーで作業する時は、作業中に変な処理が実行されない事、もしくは作業にあたりデーモンを停止しても問題ない事を確認する為に、cronの実行予定を確認するのですが、このファイルが読みづらくて、瞬時に読解する事ができません。なので、読み易く翻訳して表示するスクリプトを書いていました。当時はSolaris等のUnixLinuxで使っていました。

 

スクリプトの実行例

CentOS Stream 9

[root@hostname ~]# cronscj --help
usage: cronscj [YYYY/mm/dd]
[root@hostname ~]#
[root@hostname ~]# cronscj
2022/10/15(Sat) 00:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 01:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 02:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 03:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 04:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 05:00  user1     /home/user1/job/differential_backup.sh
2022/10/15(Sat) 05:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 06:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 07:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 08:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 09:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 10:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 11:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 12:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 13:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 14:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 15:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 16:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 17:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 18:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 19:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 20:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 21:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 22:01  root      run-parts /etc/cron.hourly
2022/10/15(Sat) 23:01  root      run-parts /etc/cron.hourly
[root@hostname ~]#

 

Perlスクリプト

[cronscj]

#!/usr/bin/perl -w
################################################################################
## CRONSCJ -- 指定された日付のCRONスケジュールをリストアップする
##
## - Author:  tomyama   Dec 2005
## - Version: 2   2005/12: Perlで書き直した
## - Version: 1   2002/06: CSHELL + awkLinux用に作成
## - Only for personal use !
################################################################################

use strict ;
use File::Basename ;
use Time::Local         qw{timelocal} ;

exit &pl_main (@ARGV) ;


sub pl_main (@) {
        &initialize (@_) ;
        &parse_arg (@_) ;

        &mk_tbl_user () ;

        &get_cronentry () ;
        foreach my $i (keys (%main::user)) {
                &get_cronentry ("$i") ;
        }

        foreach my $myindex (sort (keys (%main::cron))) {
                my ($mydate, $myuid) = (split ("_", $myindex))[0, 1] ;
                my $myuser = getpwuid ($myuid) ;
                printf ("$mydate  %-8s  $main::cron{$myindex}\n", $myuser) ;
        }

        return 0 ;
}

sub initialize (@) {
        $main::appname = basename ($0) ;

        @main::wday = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat') ;

        ($main::trgtYear, $main::trgtMonth, $main::trgtDay, $main::trgtWeek) =
                (localtime (time ()))[5, 4, 3, 6] ;
        $main::trgtYear  += 1900 ;
        $main::trgtMonth += 1 ;

        return 0 ;
}

sub parse_arg {
        my @val = @_ ;

        ## 引数分のループを回す
        while ($#val != -1) {
                my $myparam = shift (@val) ;

                if ($myparam =~ m/^(?:20[0-9]{2}[\-\/\.])?[0-9]{1,2}[\-\/\.][0-9]{1,2}$/o) {
                        my @myarg = split (/[\-\/\.]/, $myparam) ;
                        if ($#myarg < 1 || 2 < $#myarg) {
                                warn ("$myparam: unknown option\n") ;
                                &usage () ;
                                exit (1) ;
                        }
                        my $myYear = shift (@myarg) - 1900 if ($#myarg == 2) ;
                        $myYear = $main::trgtYear if (! defined ($myYear)) ;
                        my $myMonth = shift (@myarg) - 1 ;
                        if ($myMonth < 0 || 11 < $myMonth) {
                                warn ("${main::appname}: $myMonth: out of range 0..11\n") ;
                                exit (1) ;
                        }
                        my $myDay   = shift (@myarg) ;
                        if ($myDay < 1 || 31 < $myDay) {
                                warn ("${main::appname}: $myDay: out of range 1..31\n") ;
                                exit (1) ;
                        }
                        ($main::trgtYear, $main::trgtMonth,
                        $main::trgtDay, $main::trgtWeek) =
                                (localtime (timelocal (0, 0, 0, $myDay,
                                $myMonth, $myYear)))[5, 4, 3, 6] ;
                        $main::trgtYear  += 1900 ;
                        $main::trgtMonth += 1 ;
                } elsif ("$myparam" eq '-h' || "$myparam" eq '--help') {
                        &usage () ;
                        exit (0) ;
                } else {
                        &usage () ;
                        exit (0) ;
                }
        }
        return 0 ;
}

sub mk_tbl_user {
        my $passwd = '/etc/passwd' ;
        die ("${main::appname}: $passwd: file not found\n") if (! -f "$passwd") ;
        open (PASSWD, "<$passwd") ||
                die ("${main::appname}: $passwd: can not open\n") ;
        while (<PASSWD>) {
                my $buff = $_ ; chomp ($buff) ;
                $main::user{(split (":", $buff))[0]} = 1 ;
        }
        close (PASSWD) ;
        return 0 ;
}

sub get_cronentry ($) {
        my $keyuser = shift (@_) ;
        my $myCronFormat = 0 ;
        $myCronFormat = 1 if (! defined ($keyuser)) ;

        if ($myCronFormat) {
                foreach my $i ('/etc/crontab', </etc/cron.d/*>) {
                        &read_crontab ($myCronFormat, $i) ;
                }
        } else {
                foreach my $i ("/var/spool/cron/crontabs/$keyuser",
                                "/var/spool/cron/$keyuser") {
                        &read_crontab ($myCronFormat, $i) ;
                }
        }

        return 0 ;
}

sub read_crontab ($$) {
        my $myCronFormat = shift (@_) ;
        my $myCronFile   = shift (@_) ;

        return 1 if (! -f "$myCronFile") ;

        if (! -r "$myCronFile") {
                warn ("${main::appname}: $myCronFile: can not readable\n") ;
                return 1 ;
        }

        my $cron_val_User = basename ($myCronFile) ;

        open (CRONTABS, "<$myCronFile") ||
                die ("${main::appname}: $myCronFile: can not open\n") ;
        LOOP_CRONTABS: while (<CRONTABS>) {
                my $buff = $_ ; chomp ($buff) ; $buff =~ s/#.*$//o ;
                $buff =~ s/^\s+//o ;
                $buff =~ s/\s+$//o ;
                next if ("$buff" eq '') ;
                next if (! ($buff =~ m/^(?:[0-9,\*\-\/]+\s+){5}/o)) ;

                #my @tm_val = (split (/\s+/, $buff))[0..4] ;
                my @cron_val = split (/\s+/, $buff) ;

                my $cron_val_Minute = shift (@cron_val) ;
                my $cron_val_Hour   = shift (@cron_val) ;
                my $cron_val_Day    = shift (@cron_val) ;
                my $cron_val_Month  = shift (@cron_val) ;
                my $cron_val_Week   = shift (@cron_val) ;

                $cron_val_User = shift (@cron_val) if ($myCronFormat == 1) ;
                my $cron_val_UID = getpwnam ($cron_val_User) ;

                ## 曜日指定を解析
                my @myaryWeekDay = &parse_tm_val ($cron_val_Week, 0, 6) ;
                my $myflagWeekDay = 0 ;
                LOOP_WEEKDAY: foreach my $myWeekDay (@myaryWeekDay) {
                        if ($myWeekDay == $main::trgtWeek) {
                                $myflagWeekDay = 1 ;
                                last LOOP_WEEKDAY ;
                        }
                }
                next if (! $myflagWeekDay) ;

                ## 月指定を解析
                my @myaryMonth = &parse_tm_val ($cron_val_Month, 1, 12) ;
                my $myflagMonth = 0 ;
                LOOP_MONTH: foreach my $myMonth (@myaryMonth) {
                        if ($myMonth == $main::trgtMonth) {
                                $myflagMonth = 1 ;
                                last LOOP_MONTH ;
                        }
                }
                next if (! $myflagMonth) ;

                ## 日指定を解析
                my @myaryDay = &parse_tm_val ($cron_val_Day, 1, 31) ;
                my $myflagDay = 0 ;
                LOOP_DAY: foreach my $myDay (@myaryDay) {
                        if ($myDay == $main::trgtDay) {
                                $myflagDay = 1 ;
                                last LOOP_DAY ;
                        }
                }
                next if (! $myflagDay) ;

                ## 時指定を解析
                my @myaryHour = &parse_tm_val ($cron_val_Hour, 0, 23) ;

                ## 分指定を解析
                my @myaryMinute = &parse_tm_val ($cron_val_Minute, 0, 59) ;

#print ("CRONTAB: $myCronFile, ENTRY: $buff\n") ;

                foreach my $myHour (@myaryHour) {
                        foreach my $myMinute (@myaryMinute) {
                                my $keyidx = sprintf ("%04d/%02d/%02d(%3s) %02d:%02d_%07d",
                                        $main::trgtYear, $main::trgtMonth,
                                        $main::trgtDay,
                                        $main::wday[$main::trgtWeek],
                                        $myHour, $myMinute, $cron_val_UID) ;
                                my $myseq = 0 ;
                                my $keystr = undef ;
                                do {
                                        $keystr = $keyidx . '_' .
                                                sprintf ("%07d", ++$myseq) ;
                                } while (defined ($main::cron{$keystr})) ;
                                $main::cron{$keystr} = join (" ", @cron_val) ;
#print ("TIME: $keystr, $main::cron{$keystr}\n") ;
                        }
                }
        }
        close (CRONTABS) ;

        return 0 ;
}

sub parse_tm_val ($$$) {
        my $mytmstr = shift (@_) ;
        my $mytmmin = shift (@_) ;
        my $mytmmax = shift (@_) ;

        #if (! ($mytmstr =~ m/^(?:\*|(?:[0-9]+(?:-[0-9]+(?:\/[0-9]+)?)?,?)+)$/o)) {
        if (! ($mytmstr =~ m!^(?:(?:\*|(?:[0-9]+(?:-[0-9]+)?))(?:/[0-9]+)?,?)+$!o)) {
                die ("${main::appname}: ${mytmstr}: unknown format\n") ;
        }


        ## 考えられるフォーマット
        ## */10
        ## 0,5-9,10-20/5,30
        my %myhash ;
        #if ($mytmstr eq '*') {
        #       return $mytmmin .. $mytmmax ;
        #}
        foreach my $myentry (split (/,/, $mytmstr)) {
                my ($mytm, $mylaps) = (split (/\//, $myentry))[0, 1] ;
                $mylaps = 1 if (! defined ($mylaps)) ;
                my ($mytmfr, $mytmto) = (split (/-/, $mytm))[0, 1] ;
                if ($mytmfr eq '*') {
                        $mytmfr = $mytmmin ;
                        $mytmto = $mytmmax ;
                }
                $mytmto = $mytmfr if (! defined ($mytmto)) ;
                for (my $i=$mytmfr; $i<=$mytmto; $i+=$mylaps) {
                        last if ($i < $mytmmin || $mytmmax < $i) ;
                        $myhash{int ($i)} = 1 ;
                }
        }

        my @myarray ;
        foreach my $i (sort { $a <=> $b } (keys (%myhash))) {
                $myarray[$#myarray+1] = $i ;
        }

        return @myarray ;
}

sub usage {
        warn ("usage: ${main::appname} [YYYY/mm/dd]\n") ;
        return 0 ;
}