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?
answer
- cron parses the line before any shell
- one character is special in the command field
- percent ends the command
- the rest becomes standard input
- backslash escapes it, quotes do not
basics
~20 sIn 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 scron 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
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.
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.
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.
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