Why does [ -d $dirname ] match when the variable is empty or unset and why does this result in the contents of the home directory being deleted?

The unquoted $dirname is subject to word splitting, and if it's empty, it gets removed, in both [ -d $dirname ] and cd $dirname, leaving just [ -d ] and cd.

In the first one, [ sees only one argument (between [ and ]), and in that case, the test is to see if that argument is a non-empty string. -d is, so the test is true. (This is similar to how [ -z $var ] seems to work to test if $var is empty, but [ -n $var ] doesn't work at all.)

As for the cd, a plain cd will change to the user's home directory, so that's where the rm * happens.


With [[ .. ]], word splitting doesn't happen since [[ is a special shell construct, unlike [ which is just a regular command.

The solution is to double-quote the variable expansions, which you'll want to do in most cases anyway, for various reasons. Like so:

if [ -d "$dirname" ]; then
    cd -- "$dirname" && rm ./*
fi

See:

  • Why does my shell script choke on whitespace or other special characters?
  • https://mywiki.wooledge.org/WordSplitting
  • Why does parameter expansion with spaces without quotes work inside double brackets "[[" but not inside single brackets "["?

When cd is not passed an argument, it will change to the default directory (in most cases, the user's home directory, $HOME).

The interesting part of this question is that when you pass nothing to the -d operator of the bash test or [ built-in, bash appears to exit with 0 (in my opinion, this is unexpected).

When you set dirname to an empty variable, you're essentially running this:

if [ -d ]; then
    cd && rm *
fi

To me, it's surprising that the code inside this if block is executed, but I can confirm on my machine that it is. As a comment above explains, test is not interpreting the -d as an operator anymore and is simply returning 0 because it's a non-null string. One way to help guard against this kind of behavior is to make sure you quote your variables:

if [ -d "$dirname" ]; then
    cd "$dirname" && rm *
fi

In this case, if $dirname is empty, you'll be passing "" to the -d operator which correctly evaluates a non-zero exit code so the code inside the block is not executed.