1

If I have 3 different scripts to run at various times each time a file is written to, how would a bash script be written to run a specific script only at a specific time. This is not as simple as a cron job (though cron could probably swap out the .sh file based on time), I am looking for the time variables.

For instance:

If 9am-11:30 am run scriptA.sh if file.txt is changed. 
If 11:30am-5:45pm run scriptB.sh if file is changed.
If 5:46pm-8:59am run scriptC.sh if file is changed.

I asked a similar question but I don't think I was clear enough about the variables I am seeking or how to define them..

Jae Nulton
  • 373
  • 1
  • 6
  • 13
  • 1
    You might be able to do something with `date` and its formatting, turning timestamps into minutes of day. E.g. `$(date +"%H") * 60 + $(date +"%S")`. (The multiplication and addition are not proper bash, rather symbolic.) –  Jun 27 '16 at 02:10
  • 1
    But I think your cron job, with a fourth script that's just a symlink to the current relevant script, is not a bad idea at all. –  Jun 27 '16 at 02:11
  • What does "is changed" mean? Is changed since when? – Mark Reed Jun 27 '16 at 03:37
  • Meaning the file in question was last modified or accessed. I already have figured that part out though, thanks. Just working on understanding the time stuff. :) – Jae Nulton Jun 27 '16 at 03:42
  • I would have changed the symlink target for `script.sh` to be `scriptA/B/C.sh` using 3 `cron` jobs. Will that not be simpler? – anishsane Jun 27 '16 at 06:17

3 Answers3

3

The traditional tool for comparing time stamps to determine whether work needs to be performed or not is make. Its default behavior is to calculate a dependency chain for the specified target(s) and determine whether any of the dependent files have changed; if not, the target does not need to be remade. This is a great tool for avoiding recompilation, but it easily extends to other tasks.

In concrete terms, you'd create a flag file (say, .made) and specify it as dependent on your file. Now, if file has changed, .made needs to be recreated, and so make will run the commands you specify to do so. In this scenario, we would run a simple piece of shell script, then touch .made to communicate the latest (successful) run time to future runs.

What remains is for the recipe to run different commands at different times. My approach to that would be a simple case statement. Notice that make interprets dollar signs, so we need to double those dollar signs which should be passed through to the shell.

.made: file
    case $$(date +%H:%M) in \
        09:* | 10:* | 11:[0-2]? ) \
            scriptA.sh ;; \
        11:[3-5]? | 1[2-6]:* | 17:[0-3]? | 17:4[0-5]) \
            scriptB.sh;; \
        17:4[6-9] | 17:5? | 1[89]:* | 2?:* | 0[0-8]:* ) \
            scriptC.sh;; \
    esac
    touch $@   # $@ expands to the current target

The entire case statement needs to be passed as a single logical line to the shell, so we end up with those pesky backslashes to escape the newlines.

Also notice that make is picky about indentation; each (logical) line in the recipe should be preceded by a literal tab character.

The default behavior of make is to run the first target in the file; this Makefile only contains one target, so make is equivalent to make .made.

Also notice that make cares about exit codes; if scriptA, scriptB, or scriptC could exit with a non-zero exit status, that is regarded by make as a fatal error, and the .made file will not be updated. (You can easily guard against this, though.)

Community
  • 1
  • 1
tripleee
  • 175,061
  • 34
  • 275
  • 318
  • WOW! Thank you so much! You have given me a goldmine to study. I only understand about 60% of it at this moment, as I am barely past the Hello World with bash, but I had no idea that it could be that powerful. Are you accepting new clients? I don't complain and I am not picky or demanding. ;) – Jae Nulton Jun 27 '16 at 06:20
  • 1
    Not really; I am fully employed. If you are serious about this, you need another 10 reputation points and then we can [chat](https://chat.stackoverflow.com) – tripleee Jun 27 '16 at 06:25
  • Absolutely, I am obviously in way over my head. I have an idea of the pieces of the puzzle needed to get the complete package functional enough to be happy, but no idea how to get them. It is such a mess right now everything hacked and duct taped together for basic functionality... Thank you again for all the help and advice. Talk to you in a couple days if I don't get kicked off here for my questions first. :) – Jae Nulton Jun 27 '16 at 06:33
2

I see there are two issues. 1, how to determine if the current hour is within a particular range. 2, how to determine if a file has been modified, recently.

Here's how I would approach that:

#!/bin/bash

now=$( date +%s )
current_hour=$( date +%H )
file_mod=$( ls -l --time-style=+%s file.txt | awk '{print $(NF-1)}' )
file_age=$(( $now - $file_mod ))

if [[ $current_hour -gt 9 ]] && \
   [[ $current_hour -lt 11 ]] && \
   [[ $file_age -lt 3600 ]]
then
    ./scriptA.sh
fi

Here I'm using the bash operators for numeric comparison: -gt, -lt

For greater granularity with time, you would need to compute the amount of time since midnight. Perhaps something like:

hour=$( date +%H )
min=$( date +%M )
minutes=$( echo "( 60 * $hour ) + $min" | bc -l )

eleven_thirty=$( echo "( 60 * 11 ) + 30" | bc -l )
ddoxey
  • 2,013
  • 1
  • 18
  • 25
  • Excellent help! Let's say we take out the complexity of the file modification... If I manually run the script and just focus on the time aspect needing sorted. When you say greater granularity, by this you mean that your first snippet of code is a two hour window and that 11:30 is not calculated, merely the 11th hour correct? – Jae Nulton Jun 27 '16 at 03:12
  • 1
    Yes, you would need to express 11:30 AM in terms of minutes since midnight. – ddoxey Jun 27 '16 at 03:31
  • You could replace `ls` with `stat`, but unfortunately, the details of how to get a time stamp out of that aren't portable, either. For portability, maybe write a simple Perl replacement. – tripleee Jun 27 '16 at 04:01
  • I would put the file age as a simple outer condition, and then do a `case` statement on the current time. – tripleee Jun 27 '16 at 04:03
  • I am not a developer, can you describe what you mean by portability? Do you mean for use outside of this little script? Thank you for the feedback, I really am trying to take this all in. – Jae Nulton Jun 27 '16 at 04:13
  • 2
    The `--time-style` option to `ls` is not supported e.g. on Mac OSX; it is a GNU `coreutils` extension. If you need your script to run on non-Linux platforms, you need a solution which doesn't rely on this particular implementation. That's what's meant by portability. – tripleee Jun 27 '16 at 04:42
0

Well, since Bash variables can store string or integer only, "date" variables are just a string manipulations, like example below:

hours=`date +%H`
minutes=`date +%M`
sum=$(($hours + $minutes))
digit=`echo $sum | sed -e 's/\(^.*\)\(.$\)/\2/'` # counts total digits
anishsane
  • 20,270
  • 5
  • 40
  • 73
Samuel
  • 3,631
  • 5
  • 37
  • 71