0

The following line is the only line in crontab:

* 13 * * * sh /Users/gentaliaru/Dropbox/work/scripts/backup.sh >/tmp/stdout.log 2>/tmp/stderr.log

And the script contents are

rm -rf /Volumes/X5/backup/git.tar.tz
tar -cvzf /Volumes/X5/tmp/git.tar.tz /Users/gentaliaru/ws/git/
terminal-notifier -message "git Backup is complete" -title "Backup notifier"

As soon as cron starts after10-20 seconds will initiate 10+ bsdtar processes which will never end. But if I start the same command manually then it finises normally in 5 minutes or so.

I had similar problems before the macOS Monterey upgrade.

Glorfindel
  • 4,057
Infira
  • 1
  • In who's crontab? – Marc Wilson Nov 02 '21 at 19:12
  • Your crontab reads- "At every minute past hour 13". Is that how you want to run your script? – fd0 Nov 02 '21 at 19:55
  • yes but of course, the first * means minutes :) thanks for the observation. Mental note for me rtfm :) – Infira Nov 02 '21 at 20:49
  • Can you explain exactly how you confirmed that 10+ "bsdtar" processes were initated? Are you sure the script's contents are exactly as you described it here? (i.e. no shebang etc?) There seem to be some information missing. – jksoegaard Nov 02 '21 at 22:16

1 Answers1

1

* 13 * * * sh /Users/gentaliaru/Dropbox/work/scripts/backup.sh >/tmp/stdout.log 2>/tmp/stderr.log

This cron job will start at 13:00 UTC and run every minute. According to your note "it takes usually around 5 minutes" cron is starting every minute a new job before the last one has finished. In fact, its a kind of loop.

The first * has to be replaced by 0 in order to start the job every day at 13:00 only once:

0 13 * * * sh /Users/gentaliaru/Dropbox/work/scripts/backup.sh >/tmp/stdout.log 2>/tmp/stderr.log >/dev/null 2>&1

Aside the cron issue the following hints are worth to be considered. It shows how to improve the robustness and notification (even for failed jobs) for your cron job.

Always check resources (files, path, and so on) first

One common pitfall with cron are missing resources (files or paths) during the execution of a job.

Therefore, a better approach is to always specify full paths and perform some checks if files and directories exist. The whole script should look like this in order to handle existing/non-exsting paths and files properly:

#!/bin/sh

mntPoint="$(/usr/sbin/diskutil info /Volumes/X5/ | /usr/bin/grep 'Mount Point' | /usr/bin/tr -s ' ' | /usr/bin/cut -d ' ' -f 4)" if [ $mntPoint != "/Volumes/X5" ] then /usr/bin/osascript -e 'display notification "/Volumes/X5 has not been mounted" with title "Cron job aborted"' exit; fi

[[ -f '/Volumes/X5/backup/git.tar.tz' ]] && /bin/rm -rf '/Volumes/X5/backup/git.tar.tz' [[ ! -d '/Volumes/X5/backup' ]] && mkdir '/Volumes/X5/backup'

if /usr/bin/tar -czf /Volumes/X5/backup/git.tar.tz /Users/gentaliaru/ws/git/ 2> /Volumes/X5/backup/.tar.log then /usr/bin/osascript -e "display notification &quot;Backup written to /Volumes/X5/backup/git.tar.tz&quot; with title &quot;Cron job successfully completed&quot;" else # do not use /usr/bin/read as the internal read command is required! read errMsg < /Volumes/X5/backup/.tar.log /usr/bin/osascript -e "display notification &quot;$errMsg&quot; with title &quot;Cron job failed&quot;" # /path/to/terminal-notifier -message "git Backup is complete" -title "Backup notifier" fi

Explanation

First of all I recommend to convert the "plain" ascii file into a real shell script by adding a shebang line at the top of the file.

#!/bin/sh

Check if the backup drive is properly mounted

Lines 3-8 ensures the backup drive is mounted; otherwise throw a notification. A check with just [[ -d /Volumes/X5 ]] is not reliable enough as I faced sometimes (very rarely, but it happened) the mount path exists but without attached drive. Checking it with diskutil circumvent this occasional oddities.

Then, rm -rf /Volumes/X5/backup/git.tar.tz without checking the existance of the file is another pitfall. Better check the existence first:

[[ -f '/Volumes/X5/backup/git.tar.tz' ]] && /bin/rm -rf '/Volumes/X5/backup/git.tar.tz'

Furthermore, your script shows two different paths. The remove command rm in line 1 refers to /Volumes/X5/backup but the tar command in line 2 refers to /Volumes/X5/tmp/git.tar.tz. Is this really what you want?

Invoking tar to write an archive to a non-existing path/subdirectory throws the following error (at least here on HighSierra 10.13.6):

tar: Failed to open '/Volumes/X5/backup/git.tar.tz'

Therefore, check first the existence of tar's target directory and create it if it doesn't exist:

[[ ! -d '/Volumes/X5/backup' ]] && mkdir '/Volumes/X5/backup'

Line 13 does finally the backup and throws - on success - the notification (line 15). Otherwise a notification about the failure is dropped (line 19).

Use of macOS internal resources for notification

My example uses internal macOS resources without the necessity of third party tools like terminal-notifier. Its done by an AppleScript one-liner which is invoked by osascript.

If you would like to keep the solution with terminal-notifier just replace the lines (15 and 19 with osascript) with the following:

/path/to/terminal-notifier -message "Backup written to /Volumes/X5/backup/git.tar.tz" -title "Cron job successfully completed" # Line 15
/path/to/terminal-notifier -message "${errMsg}" -title "Cron job failed" # Line 19

It is also a good practice to specify the full path for terminal-notifier. In case its installed by Homebrew use something like /usr/local/bin/terminal-notifier.

Then make it executable:

chmod u+x /Users/gentaliaru/Dropbox/work/scripts/backup.sh

Sidenode for Homebrew

Paths differ between the platforms (check the documentation here):

Its /usr/local on macOS Intel and /opt/homebrew on Apple Silicon

HRitter
  • 129
  • Very thorough answer on how to avoid pitfalls in scripts called via cron. Not sure it addresses the specific issue in the question though. – nohillside Nov 03 '21 at 09:16
  • Isn't the problem in the question rather with the time definiton in the crontab? – nohillside Nov 03 '21 at 10:52
  • Besides the fact that this doesn't actually address the question, I wouldn't recommend the re-written script here. The first change of checking whether the file exists before deleting it doesn't really add anything meaningful. The second line with the check to test whether the directory exists is a good idea, but the idea of just running "mkdir" is terrible. /Volumes/ is where other file systems are mounted - if the folder is missing, it's because the file system hasn't been mounted yet for some reason. It doesn't make sense to just create a folder there. – jksoegaard Nov 03 '21 at 11:31
  • @nohillside Thanks for your comment. I've updated the answer to cover the cron issue. – HRitter Nov 03 '21 at 13:54
  • @jksoegaard You're right. Thank you for pointing me in the right direction. Check with diskutil including notification on failures added. – HRitter Nov 03 '21 at 13:57