skip to content

This line works when pasted into an interactive shell but not from a crontab: `0 3 * * * /opt/bin/dump.sh --stamp $(date +%Y%m%d)`. What does cron do with the `%` character in a crontab command, and how do you write the entry correctly?

level: middleimportance: nice to knowfreq 28%

answer

  1. cron parses the line before any shell
  2. one character is special in the command field
  3. percent ends the command
  4. the rest becomes standard input
  5. backslash escapes it, quotes do not

basics

~20 s

In a crontab, an unescaped percent sign ends the command and becomes a newline: everything after the first one is fed to the command as standard input. Escape each one as %, or move the logic into the script.

solid answer

~40 s

cron parses the crontab line itself before any shell sees it. In the command field, an unescaped `%` is special: the command stops at the first one, and the remaining text — with each further `%` turned into a newline — is handed to the command on standard input. So cron runs `/opt/bin/dump.sh --stamp $(date +` , which is an unterminated command substitution, and the shell reports a syntax error that gets mailed to the crontab owner. The fix is to backslash-escape every percent, `$(date +\%Y\%m\%d)`. The better fix is to stop putting logic in the crontab at all: let the crontab line be a schedule plus one absolute path, and compute the timestamp inside the script, where `%` is an ordinary character with no special meaning.

go deeper

for a junior

Recall that a bare percent sign in a crontab command is special and must be written as %, and that this is why a working command can fail once scheduled.

for a middle

Explain the exact behaviour — the command is cut at the first percent and the remainder is fed in on standard input — and why shell quoting cannot rescue it.

for a senior

Recognise the fingerprint quickly: works in a terminal, fails only under cron, error about unexpected end of file or an empty argument. Then argue for moving logic into the script.

for a principal

Set the convention that scheduler files carry a schedule and a path and nothing else, so no team member has to memorise cron's percent rule or systemd's %% rule to ship a job safely.

## The crontab line is not a shell line It is easy to assume the text after the five schedule fields is passed through untouched to a shell. It is not. cron parses the line first, and its parser assigns one character a meaning the shell does not give it. In the command field, an unescaped `%` terminates the command. Everything after that first percent is treated as input for the command: cron converts each subsequent `%` into a newline and writes the resulting text to the command's standard input. Only after that split does cron hand the (now truncated) command text to a shell to execute. This exists so that short interactive-style entries can feed a here-document-like body to a command without a separate script, for example mailing a fixed body or piping a canned SQL statement. It is a feature almost nobody uses deliberately and almost everybody trips over. ## Walking the broken entry ``` 0 3 * * * /opt/bin/dump.sh --stamp $(date +%Y%m%d) ``` cron cuts at the first `%`. The command becomes: ``` /opt/bin/dump.sh --stamp $(date + ``` and the text `Y` followed by newline-separated `m` and `d)` becomes standard input. The shell then tries to run a command substitution that is never closed and fails with a syntax error about unexpected end of file. Nothing runs. The error text is output, so it is mailed to the crontab owner — assuming that path works at all. The symptom varies with where the percent sits. A date format directly in an argument, `--stamp %Y-%m-%d`, produces no syntax error at all: the script simply runs with an empty value after `--stamp` and some stray text on stdin, which is a far more confusing bug than a hard failure. ## The two fixes **Escape it.** A backslash before each percent removes the special meaning: ``` 0 3 * * * /opt/bin/dump.sh --stamp $(date +\%Y\%m\%d) ``` Note that shell quoting does not help. Writing `+"%Y%m%d"` still fails, because cron's parser runs before any shell is involved and does not understand quotes. The backslash is the only escape cron recognises here. **Or take the logic out of the crontab.** The escape is easy to forget and invisible when someone copies the command back into a terminal to test it. A crontab entry is at its best when it holds a schedule and one absolute path: ``` 0 3 * * * /opt/bin/dump.sh ``` Inside the script, `%` is an ordinary character: ```bash stamp=$(date +%Y%m%d) dump_to "/backups/db-$stamp.sql" ``` This also gives you somewhere to put quoting, error handling and logging, none of which fit comfortably on one crontab line, and it means the thing you test in a terminal is byte-for-byte the thing cron runs. ## Where else percent bites The habit of treating `%` as ordinary breaks in more than one scheduler. systemd unit files use `%` to introduce specifiers such as `%i` and `%H`, so a literal percent in an `ExecStart=` line must be written `%%`. Different escape, same lesson: the file that describes the job is parsed by the scheduler, and only what survives that parse reaches a shell. Keeping the invocation trivial — one path, plain arguments — is what makes a job portable between schedulers without re-learning each one's quoting rules. ## Recognising it in the wild The fingerprint is a job that works perfectly when you paste the command into a terminal and fails only from cron, with an error that mentions an unexpected end of file, an empty argument, or a command reading input it did not expect. Scan the crontab line for a bare `%` before you start instrumenting the script.

  • Why does wrapping the format in double quotes, as `date +"%Y%m%d"`, not fix it?
    Because cron's own parser splits the line on `%` before a shell ever sees the text, and that parser has no notion of quoting. The quotes are just characters that end up on either side of the cut. Only a backslash immediately before the percent is recognised as an escape at the crontab level.
  • What legitimate use does the percent behaviour have?
    It lets a crontab entry supply standard input inline: text after the first percent is fed to the command, with further percents becoming line breaks. That is how one-line entries pipe a fixed message or a canned query into a command without a separate file. It is rarely worth using, since a script is clearer.
  • How would you make this class of bug hard to reintroduce on a team?
    Adopt the rule that a crontab entry is a schedule plus one absolute path with plain arguments, and that all logic lives in the script. That removes cron-specific quoting from the surface entirely, keeps the tested command identical to the scheduled one, and gives the same script a home under other schedulers.

saying these in an interview costs you the question

  • Assumes the crontab command field is passed to a shell untouched
  • Tries to fix it with quotes instead of a backslash
  • Blames the script when the crontab line is at fault
  • Escapes only the first percent and leaves the rest
  • Thinks % is a comment character in a crontab

context