1

In a bash script, I have some code that needs to run when the script is executed, then a function that I want executed at a specific time. How can I do that?

# Do this now
read -s -p "Password: " password

backup() {
  7z ... -p$password
}

# Do this starting at 5am - how?
while true; do
  backup
  sleep 86400
done

I've looked at at, but the man page is very scant, without any examples. I can't tell if or how to schedule a function. Do I need the extract that function to an external file?

In case this is an XY problem, what I want to do is a password-protected backup every morning at 5am. The first part of the script asks for the password via read, and feeds it via an environment variable to 7z. The archiving part can be put in a while true; loop that sleeps for 86400 seconds to implement the "daily" part. The problem is starting at 5am. cron doesn't seem like a good option because the password should be persisted in a file, which is less secure than typing it once.

2
  • 1
    In the at script, you can define a function and then call it. But if you want to do it every day, it's more suited for cron.
    – DopeGhoti
    Commented Jun 2, 2020 at 21:18
  • @DopeGhoti: cron was my first thought, but how do I pass the password to 7z securely? Commented Jun 2, 2020 at 21:44

2 Answers 2

1

If my math is right:

sleep $(( 86400 + 5*3600 - $(date +%s) % 86400 ))
echo "it's 5 in the morning (UTC)"

$(date +%s) = current time in seconds since start of 1970 (in UTC);
$(date +%s) % 86400 = the current day's portion of that, i.e. seconds since last midnight (UTC)
86400 + 5*3600 = seconds from last midnight to 5 AM tomorrow
86400 + 5*3600 - $(date +%s) % 86400 = seconds from now to 5 AM tomorrow (UTC)

Of course that breaks if the current time is between the UTC midnight and the target time; and you need to adjust for the target time for the local timezone...

Perhaps a bit more straightforward would be:

sleep $(( $(date -d '05:00 tomorrow' +%s) - $(date +%s) ))

Which also skips an extra 24 hours if it's past midnight, but at least it counts local time.

because the password should be persisted in a file, which is less secure than typing it once.

Depending on what you want to guard against, you might drop the password in some in-memory filesystem i.e. tmpfs, so it wouldn't hit the disk. On a systemd system, /run should be tmpfs, but better make sure.

Also depending on how you do the backup, you might arrange for e.g. an SSH key that can only be used to send the backups to a remote (but not read them). Or use public-key encryption so you'd only need to have the public part of the backup on the host writing the backups...

0

You cannot make at run a shell function but you can have e.g. at send a signal, wait for that signal in a running script and start the function when the signal arrives.

If this script runs as root then you do not need the --user for systemd-run.

#! /bin/bash

backup() {
  : 7z ... -p$password
}

trap sighandler_usr1 USR1

sighandler_usr1 () {
  trap '' USR1 # do not execute backup() several times in parallel
  echo "PID $$ just received SIGUSR1"
  backup
  trap sighandler_usr1 USR1
  request_signal
}

request_signal () {
  : use e.g. at or systemd-run to send SIGUSR1 to this shell
  systemd-run --user --on-calendar=05:00 kill -USR1 $$
}

request_signal

while true; do
  read -s -n 1000 ignore
done

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .