So, I deleted my home folder (or, more precisely, all files I had write access to). What happened is that I had
build="build"
...
rm -rf "${build}/"*
...
<do other things with $build>
in a bash script and, after no longer needing $build
, removing the declaration and all its usages — but the rm
. Bash happily expands to rm -rf /*
. Yea.
I felt stupid, installed the backup, redid the work I lost. Trying to move past the shame.
Now, I wonder: what are techniques to write bash scripts so that such mistakes can't happen, or are at least less likely? For instance, had I written
FileUtils.rm_rf("#{build}/*")
in a Ruby script, the interpreter would have complained about build
not being declared, so there the language protects me.
What I have considered in bash, besides corraling rm
(which, as many answers in related questions mention, is not unproblematic):
rm -rf "./${build}/"*
That would have killed my current work (a Git repo) but nothing else.- A variant/parameterization of
rm
that requires interaction when acting outside of the current directory. (Could not find any.)
Similar effect.
Is that it, or are there other ways to write bash scripts that are "robust" in this sense?
Best Answer
or
This would make the current shell treat expansions of unset variables as an error:
set -u
andset -o nounset
are POSIX shell options.An empty value would not trigger an error though.
For that, use
The expansion of
${variable:?word}
would expand to the value ofvariable
unless it's empty or unset. If it's empty or unset, theword
would be displayed on standard error and the shell would treat the expansion as an error (the command would not be executed, and if running in a non-interactive shell, this would terminate). Leaving the:
out would trigger the error only for an unset value, just like underset -u
.${variable:?word}
is a POSIX parameter expansion.Neither of these would cause an interactive shell to terminate unless
set -e
(orset -o errexit
) was also in effect.${variable:?word}
causes scripts to exit if the variable is empty or unset.set -u
would cause a script to exit if used together withset -e
.As for your second question. There is no way to limit
rm
to not work outside of the current directory.The GNU implementation of
rm
has a--one-file-system
option that stops it from recursively delete mounted filesystems, but that's as close as I believe we can get without wrapping therm
call in a function that actually checks the arguments.As a side note:
${build}
is exactly equivalent to$build
unless the expansion occurs as part of a string where the immediately following character is a valid character in a variable name, such as in"${build}x"
.