How parameter expansion helps to not accidentally delete all the files on your machine
- Published at
- Updated at
- Reading time
- 3min
While tweaking my dotfiles and writing some auto-completion shell scripts, I almost got myself into trouble.
The script in question included the following lines:
TARGET_DIR="plugins"
rm -rf "$TARGET_DIR/"
You probably know the situation. You're trying to develop a shell script. It won't be pretty but hopefully gets the job done. You google how to use an if
or how to run a loop. You're probably frustrated (I was at least), and you don't realize that your script could mess with your machine's file system because you're fiddling around with rm -rf
.
You always have to be very careful when using rm -rf
! Things can go particularly wrong when you use rm
in combination with variables. Let's imaging my fat fingers comment out the first line and I run this script. My rm -rf plugins/
command just became rm -rf /
. Ouch!
I never even considered the danger of my adventurous shell scripting until last week. ๐ So, how did I discover that danger without wiping out all files on my computer? Great question, read on!
When I write shell scripts, I heavily rely on ShellCheck because I really don't know what I'm doing. ShellCheck is like ESLint in JavaScript land, and it scans your shell scripts for common errors. You can use its online service or install the ShellCheck VSCode plugin.
In the mentioned situation, ShellCheck rescued me by telling me that I should never ever (!) use a line such as rm -rf "$SOME_VAR/"
. If $SOME_VAR
is unset or empty, I can quickly delete all files from the computer's root directory (/
). Not good!
How can you work around this danger, then?
It turns out that you can leverage parameter expansion to secure your shell scripts.
Let's look at the dangerous line again and discover secure ways that won't clean up your complete system.
# Whooops! This command wipes your file system
# if $SOME_VAR is not set ...
#
# Resulting command: rm -rf /
rm -rf "$SOME_VAR/"
You can avoid all the problems by using parameter expansion (${parameter}
). It provides three ways to secure your shell scripts.
# If $SOME_VAR is not set:
# - use "fallbackValue"
#
# Resulting command: rm -rf fallbackValue/
rm -rf "${SOME_VAR:-fallbackValue}/"
#########
# If $SOME_VAR is not set:
# - assign "fallbackValue" to $SOME_VAR
# - use "fallbackValue"
#
# Resulting command: rm -rf fallbackValue/
rm -rf "${SOME_VAR:=fallbackValue}/"
#########
# If $SOME_VAR is not set:
# - write "Not defined!" to standard error
# - exit
#
rm -rf "${SOME_VAR:?Not defined!}"
# You can also omit the message to standard error
# and just exit
rm -rf "${SOME_VAR:?}"
That's much better! The danger is resolved, and I can sleep peacefully. ๐
Shell scripting is a complicated art, and I will probably never master it (that's okay ๐). The one thing that you should take away from this post is: use ShellCheck or similar tools when you write shell scripts.
Tooling came a long way, and it's better having a tool telling you how bad your scripting is than screwing up your entire machine! Happy scripting. ๐
Join 5.5k readers and learn something new every week with Web Weekly.