Google Sheets iconSwift icon
Published at
Updated at
Reading time
3min

This post is part of my Today I learned series in which I share all my learnings regarding web development.

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!

ShellCheck – your best friend when writing shell scripts

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!

ShellCheck editor wanring: Use "${var:?}" to ensure this never expands to /.

How can you work around this danger, then?

How parameter expansion helps to define default values or to error out

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

The one thing to take away

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

Related Topics

Related Articles