We're an ISO27001:2013 Certified Supplier

blog-post-featured-image

Some of the older tools on a Linux system have historical quirks; one in particular is Vixie Cron and its string handling.

A quick look at the manual page reveals that:

Percent-signs (%) in the command, unless escaped with backslash (\), will be changed into newline characters, and all data after the first % will be send to the command as standard input. There is no way to split a single command line onto multiple lines, like the shell’s trailing “\”.

There are two major gotchas here.

Percent signs

Lets look at a contrived example.

# m h d M dw
  * * * * *  date "+%Y-%m-%d" >/tmp/lastrun && echo "Hello there!"

This simple job should output the date of the last run into a file, and if that succeeds give a warm welcome. Unfortunately – and worse, inexplicably – this fails. There’s no kind greeting, there’s a weird string in /tmp/lastrun, and best of all, there’s a cryptic error:

/bin/sh: 1: Syntax error: Unterminated quoted string

Surely all the strings in the crontab are quoted… what’s going on?

The debugging step, for many people, would be to run the command from a shell. Bash makes this work exactly as planned – date in the file, “Hello there!” output, all is well. But recall the handling of percent characters from the manual page and we see that cron is in fact executing:

date "+

with the rest of the job as standard input to date. The fix for this is to escape the percents with a backslash: \%.

Of course this behaviour can be useful. As the text following the percent sign is passed as stdin, it allows fancy things, such as running scripts which will require an input. This contrived example works more as expected:

0 0 * * * mysql jobs | mail -s "Outstanding jobs" root % select jobid,user from pending_jobs\G

New lines

Also of note is that “There is no way to split a single command line onto multiple lines”. For example, take a docker command:

docker run -d\
  --name reverse_proxy \
  -p 80:80 \
  -p 443:443 \
  -v /path/to/config/file:/etc/nginx/nginx.conf \
  -v /path/to/other/file:/etc/other/thing.yml \
  nginx:latest

Splitting this onto multiple lines is a common trick for human-readability, as otherwise docker run -d --name reverse_proxy -p 80:80 -p 443:443 -v /path/to/config/file:/etc/nginx/nginx.conf -v /path/to/other/file:/etc/other/thing.yml nginx:latest is rather unfriendly.

It may be tempting to do the same in a crontab – insert a newline every so often just to tidy things up a little, but that invalidates the job. The bits wrapped onto a new line will be interpreted as new jobs.

In any case, this job doesn’t tell us much about the intentions of its author. One way to keep things obvious might be to put those lines in a script, say /usr/local/sbin/start_reverse_proxy and call that from cron instead.

Photo by Agê Barros on Unsplash