29

Sometimes I use, $PROJECT_HOME/* to delete all files in the project. When the environment variable, PROJECT_HOME is not set (because I did su and the new user doesn't have this environment variable set), it starts deleting all files from the root folder. This is apocalyptic.

How can I configure bash to throw error, when I use an undefined environment variable in the shell?

  • 6
    set -u will do what you want. – cuonglm Jun 08 '15 at 06:27
  • can you make it as the answer? –  Jun 08 '15 at 06:27
  • [ -z "$PROJECT_HOME" ] || rm -r "$PROJECT_HOME"/* won't lead to your apocalypse, ever. (set -u still may). Check out my answer. – Petr Skocik Jun 08 '15 at 07:36
  • @PSkocik, i agree, your answer is informative but is not practical to use as it is very long and i would rather avoid initialising empty env.vars –  Jun 08 '15 at 07:50
  • 2
    Of course you wouldn't initialize your vars to empty strings. [ -z "$VAR" ] works with an uninitialized VAR too. The initialization was just to show the undesirable behavior—My point is, if your vars ever do become initialized to empty strings, in whatever way, and you run rm -r "$PROJECT_HOME"/* mistakenly relying on set -u, you will get the "apocalyptic" behavior. IHMO, it's better to be safe than sorry when it comes to protecting the entire contents of your computer. set -u is not safe. – Petr Skocik Jun 08 '15 at 08:00
  • 3
    "It is very long"? You should not be looking for a convenient way to manually perform dangerous operations. Instead, you should create a function, alias, or script to do what you want; in this case, making in an alias from @PSkocik's suggested command will be both safe and convenient. – Kyle Strand Jun 08 '15 at 17:32
  • 1
    What if the user sets PROJECT_HOME=/etc? Just checking for an empty value is not enough to prevent cataclysm. You shouldn't use variables from untrusted users when running as root. – Barmar Jun 10 '15 at 19:40

3 Answers3

32

In POSIX shell, you can use set -u:

#!/bin/sh

set -u
: "${UNSET_VAR}"

or using Parameter Expansion:

: "${UNSET_VAR?Unset variable}"

In your case, you should use :? instead of ? to also fail on set but empty variables:

rm -rf -- "${PROJECT_HOME:?PROJECT_HOME empty or unset}"/*
cuonglm
  • 153,898
17
[ -z "$PROJECT_HOME" ] || rm -r "$PROJECT_HOME"/*

This will also catch the case where PROJECT_HOME is set but doesn't contain anything.

Example:

1) This will delete pretty much everything you can delete on your system (barring dotfiles inside / (there aren't usually any)):

set -u
PROJECT_HOME=
rm -r "$PROJECT_HOME"/*

2) This won't do anything:

PROJECT_HOME=
[ -z "$PROJECT_HOME" ] || rm -r "$PROJECT_HOME"/* 

Completely removing your project home and recreating it might be another option (if you want to get rid of dotfiles too):

#no apocalyptic threats in this scenario
rm -r "$PROJECT_HOME"
mkdir "$_" 
Petr Skocik
  • 28,816
  • 1
    -z is ok, but since $PROJECT_HOME is supposed to be a directory, maybe -d would be better. [[ -d $PROJECT_HOME ]] && rm -r "$PROJECT_HOME". – kojiro Jun 08 '15 at 11:41
  • 2
    If PROJECT_HOME is set to a nondirectory, that's an noncatastrophic error, possibly due to a typo. The -d check would hide that error. I think it's better if it goes on to rm and rm complains about it out loud. – Petr Skocik Jun 08 '15 at 11:50
  • 2
    If PROJECT_HOME is set, but the name is not a directory, then [[ -d $PROJECT_HOME ]] && rm -r "$PROJECT_HOME" will do nothing, silently. But [[ -z $PROJECT_HOME ]] || rm -r "$PROJECT_HOME" will silently delete "$PROJECT_HOME", even if it's a file that is not a directory. Getting an error message is never a problem: if [[ -d $PROJECT_HOME ]]; then rm -r "$PROJECT_HOME"; else printf '%s is not a directory\n' "$PROJECT_HOME" >&2; fi – kojiro Jun 08 '15 at 13:30
  • @kojiro What follows after the check is rm -r "$PROJECT_HOME"/*. (notice the /* part). rm will always fail with that it PROJECT_HOME isn't a directory. But those are details. – Petr Skocik Jun 08 '15 at 13:45
  • 1
    Now "accidentally" set PROJECT_HOME="/." ... – Hagen von Eitzen Jun 08 '15 at 15:13
  • 1
    @HagenvonEitzen If PROJECT_HOME is set to root, the procedure that empties PROJECT_HOME will empty root. That's the expected and perfectly reasonable behavior. And you don't even need that final dot. – Petr Skocik Jun 08 '15 at 15:28
  • @PSkocik - you're wrong about rm failing in every case that $PROJECT_HOME doesn't point to a directory file w/ /*. And anyway, all of the suggestions in this answer are representative of bad habits, in my opinion. I say this because I've learned the hard way to break myself of those very same habits. The simple fact of the matter is - you should really never prune a tree not already rooted in . - you just don't do it. In fact, you really shouldn't attempt to do modifications on any files which aren't already./ here. Basically I'm saying - prune only from the base, like find. – mikeserv Jun 28 '15 at 00:35
  • @mikeserv All you gotta do to prove me wrong is give one single example of a non-zero-length PROJECT_HOME that isn't a directory for which rm -r $PROJECT_HOME/* can succeed. – Petr Skocik Jun 28 '15 at 00:50
  • 1
    @PSkocik - that's pretty easy - put the name of a symlink in it. – mikeserv Jun 28 '15 at 06:07
  • @mikeserv OK :D But it's still got to be a DIRECTORY symlink. – Petr Skocik Jun 28 '15 at 10:25
  • @PSkocik - it's also actually not expected or normal behavior for rm to operate on /.. rm is spec'd to refuse - even with -f - to handle any arg ending with /.. or /. or which is only . or ... That's another reason why ./. is the safest place to be when working w/ rm. – mikeserv Jun 28 '15 at 14:41
0

Another way to do this:

rm -r "${somevar:-/tmp/or_this_if_somevar_is_empty}"/*

There are many variable substitutions, the one above is when "somevar" is empty (and in that case it attempts to delete /tmp/or_this_if_somevar_is_empty/* )