Storing the last invocation time as the last modification time of a file
perl
#! /usr/bin/perl
# usage: cmd file seconds cmd args
$file = shift @ARGV;
$time = shift @ARGV;
$age = -M $file;
exit 3 if defined($age) && 86400 * $age < $time;
open $fh, ">>", $file ||
die "Can't open $file: $!\n";
utime undef, undef, $fh;
exec @ARGV;
To be used like:
that-script /some/file 10 androidpush -msg 'There was a motion in your flat!'
It records the time of last run as the last modification time of /some/file
and doesn't run the command if the age of that file is less than the specified time.
shell
With BSD or GNU find
, you could do it with:
#! /bin/sh -
file=${1?} time=${2?}
shift 2
[ "$(find -- "$file" -prune -newermt "$time ago")" ] && exit 3
touch -- "$file"
exec "$@"
To run as:
that-script /some/file 10sec androidpush -msg 'There was a motion in your flat!'
In any case, you'll have to store the information about the last run in some place that persists for the next runs. The file system is an obvious place for that. That's one where you can reserve some area for yourself.
sleep process with specific name
Another namespace could be for instance process names:
#! /bin/bash -
label=$0-$(logname)@${1?} time=${2?}
shift 2
pgrep -f "^$label" > /dev/null 2>&1 && exit 3
(exec -a "$label" sleep "$time") &
exec "$@"
To be used as:
that-script motion 10 androidpush -msg 'There was a motion in your flat!'
(assuming a sleep
implementation that doesn't care about its argv[0]
).
We're using bash
instead of sh
for its exec -a arg0
. If you don't have bash
, other shells that support that include ksh93
, mksh
, yash
and zsh
. Or you could revert to perl
again.
Note that that namespace is not reserved. There's nothing preventing another user to create a process with that same name (as opposed to using a ~/.lastrun
file in the file-based approach), however given that here those scripts are all started by the same motion
process, you could restrict the search for process to those with the same parent process id:
Improvement for when all the scripts are always started by the same process
#! /bin/bash -
label=$0-${1?} time=${2?}
shift 2
pgrep -P "$PPID" -f "^$label" > /dev/null 2>&1 && exit 3
(exec -a "$label" sleep "$time") &
exec "$@"
Linux-only: use a kernel key with a timeout
On Linux, your could also use a key on the user's session keyring. That has not really be designed for that, but here it comes handy as those keys persist across time and can be given a timeout:
#! /bin/sh -
key=$0-${1?} time=${2?}
shift 2
keyctl search @us user "$key" > /dev/null 2>&1 && exit 3
key=$(keyctl add user "$key" x @us) || exit
keyctl timeout "$key" "$time" || exit
exec "$@"
Now, it doesn't really matter in your case as you're not running two instances of that script at the same time, but all of those have a race conditions which makes that it doesn't guarantee that two instances won't be run within the specified time. If two instances are run at the same time, they could both verify that the condition is OK before any one of them resets it.
Locking a file to avoid the race condition
An approach that would work around that would be to have the sleep
process hold a lock on a file:
#! /bin/sh -
file=${1?} time=${2?}
shift 2
{
flock -n 3 || exit 3
sleep "$time" 3>&3 &
exec "$@"
} 3<> "$file"
(in addition here, both sleep
and the command being run would hold the lock which would make sure not two instances of the command run at the same time even if they take longer to run than the sleep
command).