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.