What does SC2115 mean?
SC2115, Use "${var:?}" to ensure this never expands to /, is ShellCheck's warning-level finding on an rm -rf whose path is a variable followed by / or /*. If that variable is unset or empty, the path collapses to / or /* and rm starts at the root. The message names the fix: the :? parameter expansion, which stops the script before rm runs.
What actually breaks?
A staging tree with two releases in it and a script meant to clean one of them, run on this box with bash 5.3.9:
Both releases gone, "cleaned", exit 0. Strict mode is on and did nothing: RELEASE is set, to an empty string, so set -u has no complaint, and rm succeeded, so set -e has none either. The path became …/stage//*, the glob matched v1.4 and v1.5, and rm -rf removed them.
The same shape with an absolute prefix is the one that empties a server. Never run this; the expansion is enough:
That is the command line rm would have received.
What does ShellCheck say, and what is the fix?
Guard both variables on the rm line itself:
With no argument the script stops on line 5 with your message and touches nothing. With v1.4 it cleans exactly that directory. The text after :? is optional; without it bash prints parameter null or not set, which is accurate and unhelpful at 3 a.m.
Why does set -u not catch this?
Because empty is not unset:
set -u guards against a variable that was never assigned. A RELEASE= line in a config file, a ${1:-} default, a $(git describe) that printed nothing: all of those assign an empty string, and set -u passes every one. ${RELEASE:?} rejects unset and empty both. ${RELEASE?}, without the colon, rejects only unset and is the wrong tool here.
Which forms trigger SC2115?
Run through ShellCheck 0.11.0, one per line:
| Line | Result |
|---|---|
rm -rf "$BUILD_DIR/"* | SC2115, expands to /* |
rm -rf "$BUILD_DIR"/* | SC2115, expands to /* |
rm -rf "/$BUILD_DIR/" | SC2115, expands to / |
rm -rf /$BUILD_DIR/ | SC2115 plus SC2086 for the missing quotes |
rm -rf "$STAGE/$RELEASE/"* | SC2115, expands to /* |
rm -rf "${BUILD_DIR:?}/"* | clean |
rm -rf "$BUILD_DIR" | clean |
The last row is not an oversight. With an empty $BUILD_DIR, rm -rf "" gets no usable path and fails; there is no slash to turn it into the root. It is still a bug, and set -u or :? is still the right guard, but it is not the catastrophic one this code exists for.
When should I disable SC2115, and how?
Disable it only when the same variable was validated a few lines earlier and you would rather not repeat the guard. Even then the guard on the rm line is free, so the directive is a matter of taste rather than need:
For one run, shellcheck --exclude=SC2115 script.sh; for a project, disable=SC2115 in .shellcheckrc; to lint for this rule and nothing else, shellcheck --include=SC2115 script.sh, which is a reasonable CI gate on any repo that has rm -rf in it.
Related codes
- SC2114 — an
rm -rfon a literal system directory such as/usr. - SC2164 —
cdwithout|| exit, so a laterrmruns in the wrong directory. See Bash Error Handling. - SC2086 — the unquoted form in the table above. See the SC2086 deep dive.
- SC2154 — the variable was never assigned at all. See the SC2154 deep dive.
Any other code: paste it into the ShellCheck Error Decoder.