Writing a Robust Script
Goal
You will implement, one by one, the conditions for a script that can be put on a production server: it does not hide failures, it guarantees cleanup, and it is safe to run twice.
Why it matters
The shell is tolerant by default. A variable that does not exist passes through as an empty string, a failure in the middle of a pipe is buried if the last command succeeds, and temporary files remain even when a script dies. This tolerance creates accidents such as "the backup that was reported as a success every day was actually 0 bytes". set -euo pipefail is a mechanism that stops a problem at the point where it is discovered, and trap ... EXIT is a mechanism that makes cleanup run however the script ends. The two only make sense when used as a pair.
Steps
- Create
/root/robust/strict.sh. Putset -euo pipefailat the very top, take one argument, and print it as it is.strict.sh hellomust printhelloand exit with 0, and running it without arguments must end with a nonzero code. - Create
/root/robust/pipe.sh. If the argument isok, it runs a pipeline that succeeds, and if it isbroken, it runs a pipeline in which an earlier stage fails. Forbroken, the exit code must not be 0. - Create
/root/robust/tmpwork.sh. Create a temporary file withmktemp, print its path on one line to standard output, and be sure to delete it withtrapon exit. If you givefailas the argument, it must end in failure, but even then the temporary file must not be left behind. - Create
/root/robust/once.sh. When run, it works for about 3 seconds and ends with 0. If you run it again while it is already running, it must reportalready runningand end with exit code 9. After the earlier run has finished, it must run normally again. - Create
/root/robust/count.sh <디렉터리>(the argument is a directory). It prints only the number of regular files directly under that directory. It must be accurate even if both the directory path and the file names contain spaces, and it must end with a nonzero code when there is no argument. - Create
/root/robust/backup.sh <원본디렉터리> <결과파일>(the source directory, then the output file).- If there are no arguments, print a message that includes
usageto standard error and exit with exit code64 - If the source path does not exist, exit code
66 - If all is well, create a tar.gz including subdirectories and exit with exit code
0
- If there are no arguments, print a message that includes
- Create
/root/robust/retry.sh <횟수> <명령...>(the number of attempts, then the command). If the command fails, retry up to that many times, but rest at least 1 second between attempts, and stop immediately on success. Leave a log containing the wordattempton every attempt. - Create
/root/robust/audit.sh <스크립트경로>(the argument is a script path). It checks whether the target script has a shebang and a strict mode declaration, and prints two lines,strict=yes|noandshebang=yes|no. If both are yes, the exit code is 0; otherwise it is a nonzero code. It must also be a nonzero code when the file does not exist.
Notes
- In the
traphandler, if you do not grab the exit code withlocal rc=$?at the very start and restore it at the end withexit "$rc", the trap swallows the exit code. - You can count only top-level regular files with
find "$d" -maxdepth 1 -type f. - Common mistake 1: if in step 4 you create a lock file and never delete it, the next run is blocked forever.
- Common mistake 2: if in step 7 you re-run the command with
$*, arguments that contain spaces break.
Turn on strict mode
Create /root/robust/strict.sh. Put set -euo pipefail at the very top, take one argument, and print it as it is. strict.sh hello must print hello and exit with 0, and running it without arguments must end with a nonzero code.
One line, set -euo pipefail, is enough. With -u in effect, the script must die the moment it reads the nonexistent $1.
Propagate pipeline failures
Create /root/robust/pipe.sh. If the argument is ok, it runs a pipeline that succeeds, and if it is broken, it runs a pipeline in which an earlier stage fails. For broken, the exit code must not be 0.
By default the shell looks only at the status of the last command. Make it end in failure even when an earlier stage fails.
Always delete the temporary file
Create /root/robust/tmpwork.sh. Create a temporary file with mktemp, print its path on one line to standard output, and be sure to delete it with trap on exit. If you give fail as the argument, it must end in failure, but even then the temporary file must not be left behind.
trap ... EXIT runs on both normal and error exits. Create the temporary file with mktemp.
Prevent duplicate runs
Create /root/robust/once.sh. When run, it works for about 3 seconds and ends with 0. If you run it again while it is already running, it must report already running and end with exit code 9. After the earlier run has finished, it must run normally again.
Use a flock or mkdir based lock. The second run must end immediately with exit code 9, and the lock must be released when the earlier run finishes.
Safely handle paths that contain spaces
Create /root/robust/count.sh <디렉터리> (the argument is a directory). It prints only the number of regular files directly under that directory. It must be accurate even if both the directory path and the file names contain spaces, and it must end with a nonzero code when there is no argument.
There are spaces in both the path and the file names. If you do not wrap the variables, the count goes wrong.
Usage and the exit code convention
Create /root/robust/backup.sh <원본디렉터리> <결과파일> (the source directory, then the output file).
- If there are no arguments, print a message that includes
usageto standard error and exit with exit code64 - If the source path does not exist, exit code
66 - If all is well, create a tar.gz including subdirectories and exit with exit code
0
Send the usage message to standard error, with a different exit code for each situation. 64 is a usage error and 66 is no input.
Retry helper
Create /root/robust/retry.sh <횟수> <명령...> (the number of attempts, then the command). If the command fails, retry up to that many times, but rest at least 1 second between attempts, and stop immediately on success. Leave a log containing the word attempt on every attempt.
To re-run the command exactly as given, you need "$@". Stop immediately on success, and rest between attempts.
Script audit tool
Create /root/robust/audit.sh <스크립트경로> (the argument is a script path). It checks whether the target script has a shebang and a strict mode declaration, and prints two lines, strict=yes|no and shebang=yes|no. If both are yes, the exit code is 0; otherwise it is a nonzero code. It must also be a nonzero code when the file does not exist.
Read another script file and judge whether it has a shebang and strict mode. You must also handle files that do not exist.