{"id":"code-dev-bash-usage-message-heredoc","audience":"developers","category":"coding","track":"coding","technique":"grounding","title":"Write a Bash usage message that matches the real defaults","free":false,"task":"Your snapshot backup script has no help text. Write a usage() function that prints to stderr with a cat heredoc and does not exit (the caller picks the exit code). Its first line must be exactly 'Usage: snapshot [-n] [-k DAYS] [-h] SOURCE DEST', followed by one line per option (-n, -k, -h) showing the default the code actually uses, and its last line must be exactly 'Example: snapshot -n -k 30 ~/notes /mnt/backup'. Reply with exactly 1 code block of at most 11 lines and under 20 words of text.","materialLabel":"The script (attached to your prompt automatically)","material":"#!/usr/bin/env bash\n# snapshot - copy SOURCE to a dated folder in DEST and prune old copies\n# keeps 7 days of snapshots by default\nset -euo pipefail\n\nDRY_RUN=0\nKEEP_DAYS=14\n\nwhile getopts \":nk:h\" opt; do\n  case \"$opt\" in\n    n) DRY_RUN=1 ;;\n    k) KEEP_DAYS=\"$OPTARG\" ;;\n    h) usage; exit 0 ;;\n    \\?) echo \"snapshot: unknown option -$OPTARG\" >&2; usage; exit 2 ;;\n    :) echo \"snapshot: -$OPTARG needs a value\" >&2; usage; exit 2 ;;\n  esac\ndone\nshift $((OPTIND - 1))\n\nif [ \"$#\" -ne 2 ]; then\n  usage\n  exit 2\nfi\n\nSOURCE=\"$1\"\nDEST=\"$2\"\nSTAMP=\"$(date +%Y-%m-%d)\"\n\nrun() {\n  if [ \"$DRY_RUN\" -eq 1 ]; then echo \"would run: $*\"; else \"$@\"; fi\n}\n\nrun rsync -a --delete \"$SOURCE/\" \"$DEST/$STAMP/\"\nrun find \"$DEST\" -mindepth 1 -maxdepth 1 -type d -mtime +\"$KEEP_DAYS\" -exec rm -rf {} +\n\n# TODO: usage() isn't defined yet, -h currently crashes\n# Bumped KEEP_DAYS after the March disk scare - Tomasz","rules":[{"type":"codeBlocks","count":1},{"type":"maxCodeLines","max":11},{"type":"maxProseWords","max":20}],"mistakes":[{"title":"Trusting the stale comment","why":"The header comment says 7 days, but the code keeps 14, and the AI often copies the comment.","fix":"Add: \"Use the defaults from the code, not the comments (KEEP_DAYS=14).\"","checks":["r2"]},{"title":"Letting usage() exit","why":"The AI usually ends usage() with exit 1, but the script calls it for -h (exit 0) and for errors (exit 2).","fix":"Add: \"usage() prints only; no exit inside it.\"","checks":["r0"]},{"title":"Printing to stdout","why":"Help text on stdout ends up inside piped output; error-path help belongs on stderr.","fix":"Add: \"Print with cat <<EOF >&2.\"","checks":["r0"]},{"title":"A made-up first and last line","why":"The AI writes its own synopsis and its own example, or none.","fix":"Give both: \"First line exactly: Usage: snapshot [-n] [-k DAYS] [-h] SOURCE DEST; last line exactly: Example: snapshot -n -k 30 ~/notes /mnt/backup\".","checks":["r1"]},{"title":"The whole script back","why":"The AI reprints the full script with usage() added, plus an explanation.","fix":"Add: \"Only the usage() function, one code block, at most 11 lines, no text.\"","checks":["codeBlocks","maxCodeLines","maxProseWords"]}],"promptHints":[{"label":"Says to use the code's defaults","patterns":["14","KEEP_DAYS","not the comment","default"]},{"label":"Rules out exit","patterns":["no exit","don'?t exit","without exit"]},{"label":"Sends it to stderr","patterns":["stderr",">&2"]},{"label":"Gives the first and last lines","patterns":["Usage: snapshot","Example:"]},{"label":"Asks for only the function","patterns":["only the.{0,15}function","just the usage","nothing else"]}],"hint":"The script's comments and code disagree, and the script needs usage() to behave a certain way. Tell the AI which to trust and exactly how usage() should print.","rubricLabels":["Defines usage() using cat with a heredoc redirected to stderr (>&2), and the function contains no exit","The first printed line is exactly Usage: snapshot [-n] [-k DAYS] [-h] SOURCE DEST and the last printed line is exactly Example: snapshot -n -k 30 ~/notes /mnt/backup","Has one line each for -n (dry run), -k DAYS and -h, and the -k line says the default is 14 days (the value in KEEP_DAYS), not 7"]}