frankc
frankc

Reputation: 11473

Find cron jobs that run between given times

Is it possible to find all entries in a crontab that run between time X and time Y without having to parse the cron time entries myself? I'm mainly concerned with time hour and minute, not so much the other 3 time fields.

Upvotes: 4

Views: 5179

Answers (4)

nohat
nohat

Reputation: 7281

You can use the Ruby 1.8.7 gem "crontab-parser" to parse a crontab, and then iterate through each minute between the two timestamps, calling should_run? on every crontab entry:

require 'rubygems'
require 'crontab-parser'

start_time = Time.parse(ARGV[0])
end_time   = Time.parse(ARGV[1])

# CrontabParser barfs on env variable setting in a crontab, so just skip them
cron_data = File.readlines(ARGV[2]).select {|line| line =~ /^[0-9*]/}
cron  = CrontabParser.new(cron_data.join("\n"))
(start_time..end_time).each do |timestamp|
  next unless timestamp.sec.to_i == 0
  cron.each do |cron_entry|
    if cron_entry.should_run?(timestamp)
      puts "#{timestamp}\t#{cron_entry.cmd}"
    end
  end
end

So, if you have a crontab.txt that looks like this:

*   *   *   *   *   foo
18  15  *   *   *   bar

Then call the script with output from date or similar and a file containing a crontab:

ruby cron_report.rb "Sat Mar 3 02:07:32 UTC 2012" "Sat Mar 3 02:21:32 UTC 2012" crontab.txt

You'll get output like this:

Sat Mar 03 15:17:00 UTC 2012    *   *   *   *   *   foo
Sat Mar 03 15:18:00 UTC 2012    *   *   *   *   *   foo
Sat Mar 03 15:18:00 UTC 2012    18  15  *   *   *   bar
Sat Mar 03 15:19:00 UTC 2012    *   *   *   *   *   foo
Sat Mar 03 15:20:00 UTC 2012    *   *   *   *   *   foo

Upvotes: 2

frankc
frankc

Reputation: 11473

Since this doesn't seem possible without parsing cron, I decided to write it myself in perl:
(not sure why the formatting is fubar)


#!/usr/bin/perl -w
use strict;
use Set::CrossProduct;

my $begin; my $end;

if($ARGV[0] && $ARGV[0] =~ /before/i){ $begin = 0; $end = $ARGV[1];
} elsif($ARGV[0] && $ARGV[0] =~ /after/i){ $end = 2400; $begin = $ARGV[1]; } else{ $begin = $ARGV[0]; $end = $ARGV[1]; }

if(!defined($begin) || !defined($end)){ print STDERR "Invalid Arguments\n"; exit 1; }

my @crontab = `crontab -l`;

foreach my $cronjob (@crontab){ chomp $cronjob;

next if $cronjob =~ /^ *\#/ ||$cronjob =~ /^ *$/  ;

#print "in: $cronjob\n";

my ($min,$hour,$day_of_month,$month,$day_of_week, @cmd) = split(/ /, $cronjob);

my @mins = expandRange($min,0,59);
my @hours = expandRange($hour,0,23);

my $cp = Set::CrossProduct->new([\@hours,\@mins]);

my $combos = $cp->combinations();

foreach my $time ( map { $_->[0]*100 + $_->[1] } @$combos){
if($time >= $begin && $time <= $end){
    print $cronjob,"\n";
    last; #don't print the job n times, just once
}
}

}

sub expandRange{

my ($in,$begin,$end) = @_;
#print "in: ($in)[$begin:$end]\n";
my @range;

my @vals = split(/,/,$in);
foreach my $val (@vals){
my $mult = 1;
if($val =~ /\/(.+)$/){
    $mult = $1;
    $val =~ s/\/(.+)//;
}

if($in =~ /\*/){
    @range = grep { $_ % $mult == 0 && $_ >= $begin &&  $_ <= $end  } $begin..$end;
}
elsif($val =~ /[\-:]/){
    my ($first, $last) = split(/[\-:]/,$val);
    push(@range, grep {  $_ % $mult == 0 && $_ >= $begin &&  $_ <= $end } $first..$last);
}
elsif($val >= $begin &&  $val <= $end) {
    push(@range, $val);
}
}

my %unique;
@unique{@range} = 1;

return sort keys %unique;

}

Upvotes: 2

sorpigal
sorpigal

Reputation: 26086

Anything is possible but you will have to parse crontab yourself.

There are no simple answers, but just because I can here's a partial solution in bash.

    #!/bin/bash

    start="${1-0:0}"
    end="${2-0:0}"
    start_hour=$(cut -d: -f1 <<<"$start")
    end_hour=$(cut -d: -f1 <<<"$end")
    start_min=$(cut -d: -f2 <<<"$start")
    end_min=$(cut -d: -f2 <<<"$end")

    # leading zeroes would be bad
    let start_hour=10#${start_hour}
    let end_hour=10#${end_hour}
    let start_min=10#${start_min}
    let end_min=10#${end_min}

    cat /etc/crontab | \
            grep -v ^\# | \
            grep -E -v '^([a-zA-Z]+)' | \
            awk '{print $1, $2, $7}' | \
    while read line ; do
            if [ ! -z "$line" ] ; then
                    h=$(cut -d' ' -f2 <<<"$line")
                    m=$(cut -d' ' -f1 <<<"$line")
                    cmd=$(cut -d' ' -f3- <<<"$line")

                    if [ "$h" = '*' ] || ( [ $h -ge $start_hour ] && [ $h -le $end_hour ] ) ; then
                            if [ "$m" = '*' ] || ( [ $m -ge $start_min ] && [ $m -le $end_min ] ) ; then
                                    echo $cmd
                            fi
                    fi
            fi
    done

Call like

cron_between 09:00 16:59

This certainly won't work for complex time specifications (e.g. */2) and only reports on the first part of the command. All of this can be corrected, but probably you'd be better off doing it in perl or something.

Upvotes: 4

Scott Harwell
Scott Harwell

Reputation: 7465

You could copy and paste the crontab file into Excel and then create a few functions that do this. Since the time fields are always in the same place, your columns in Excel would correspond to the timeframe in which that command is executed.

You'll have to write your formula so that is accounts for single integer values and repeating values (like 10 versus */10).

If your crobtab file changes a lot and you have many entries, then you could probably write a php script to parse this information pretty quickly.

Upvotes: 1

Related Questions