TT Lab
Get started
Learn Learning paths Courses

Shell Scripting

Writing a Robust Script

Continue in TT Lab

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. Create /root/robust/backup.sh <원본디렉터리> <결과파일> (the source directory, then the output file).
    • If there are no arguments, print a message that includes usage to standard error and exit with exit code 64
    • 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
  7. 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.
  8. 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.

Notes

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).

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.