skip to content

Scripting Best Practices

The difference between a snippet that works on your laptop and a script you are willing to run from cron on a production host: strict mode, real error handling, linting, deliberate portability choices, and predictable CLI behavior. Senior shell interviews spend most of their time in this area.

part ofBashoverview, primer and where to startread it →
on this pageshow

questions

page 2 of 2

Pull requests that touch the team's bash scripts keep collecting comments about indentation and where `then` should go. What does the shfmt formatter do that ShellCheck does not, and how would you run it so CI fails on unformatted files without rewriting them?

level: juniorimportance: nice to knowfreq 30%

basics

~20 s

shfmt is a formatter: it rewrites shell scripts into a canonical layout, while ShellCheck is a linter that finds bugs and never changes the file. In CI use shfmt -d, which prints a diff and exits non-zero instead of writing.

open as a page

In bash, what changes when a getopts optstring begins with a colon, as in `while getopts ":f:v" opt`, compared with `"f:v"` — and how does the script then tell an unknown option from a missing option value?

level: middleimportance: nice to knowfreq 40%

basics

~20 s

A leading colon puts getopts in silent error mode: bash stops printing its own diagnostics, an unknown option sets the variable to ? and a missing value sets it to :, with the offending option letter in OPTARG so the script can report both itself.

open as a page

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%

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.

open as a page

A bash script dies with `deploy.sh: line 87: ...` inside a helper function that is called from a dozen places, so the line number alone does not tell you which call path got there. Which bash builtin and which shell arrays let a script print its own call stack?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Bash keeps a call stack you can read: the caller builtin walks it one frame at a time, and the parallel arrays FUNCNAME, BASH_SOURCE and BASH_LINENO expose it directly, so a script can print which function called which, from which file and line.

open as a page

In a bash script, what does `trap 'handler' ERR` give you that `set -e` alone does not, and why is it usually paired with `set -E`?

level: seniorimportance: nice to knowfreq 32%

basics

~20 s

An ERR trap runs your handler on the same failures that make set -e abort, so the script can report the failing line, command and status instead of dying silently. set -E (errtrace) is needed because otherwise the ERR trap is not inherited by shell functions, command substitutions or subshells.

open as a page

A deploy script runs `ssh "$host" "rm -rf /srv/app/$release"` where $release comes from a CI variable. The local quoting looks right, so why is this still a command-injection hole, and how do you pass an untrusted value to a remote command safely?

level: seniorimportance: nice to knowfreq 38%

basics

~20 s

ssh does not send an argument vector; it joins its command arguments into one string that the remote login shell parses again. Local quotes only govern the local parse, so metacharacters in the value become remote syntax. Validate the value, or pass it on stdin.

open as a page

A team standard says every shell script in the repo must be POSIX-compliant `#!/bin/sh` "for portability". How would you decide whether that policy is right, and what does writing POSIX sh actually cost you?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Decide from the list of environments the script must actually run in. POSIX sh buys you machines where bash is absent, but costs arrays and [[ ]], which often makes the code less safe rather than more. Guaranteeing bash is usually cheaper.

open as a page

You own a bash deployment script that colleagues rerun by hand after it fails halfway. What does making that script idempotent actually require, and how do you decide how far to take it?

level: principalimportance: nice to knowfreq 32%

basics

~20 s

Idempotence is about the end state, not the exit status: a second run must leave the system in the same shape as the first, whether or not the first finished. That means classifying every step and converting the ones that accumulate rather than converge.

open as a page

showing 31–38 of 38