8

I was writing some scripts and wrote something like

ARTIFACTS="/SOME/PATH"
[ -d $ARTIFCATS ] && rm -rf $ARTIFACTS/*

What happened is that out of stupidity I executed the second line without executing the first. It turned out that [ -d "" ] returns true and the expression became

rm -rf /*

Luckily it was only a test machine and I wasn't a sudo, but though I lost some data

My question is, why [ -d "" ] return true?? the documentation clearly states it checks whether a path exists and is a folder

I solved the problem by using

[ -e $ARTIFACTS ]
which seems to work

Cheers

3
  • 5
    Or maybe you executed both lines. In the code example above, you never set ARTIFCATS.
    – Buhb
    Oct 8, 2013 at 13:00
  • 2
    I would just write that as rm -rf $ARTIFACTS without the /*. This would also delete the $ARTIFACTS directory, which is fine, because if i want to be sure that it exists before putting something in it, i will execute mkdir -p $ARTIFACTS anyway. It will also delete hidden files inside $ARTIFACTS, which is also fine, because i wouldn't write rm -rf $ARTIFACTS/* if $ARTIFACTS contained anything i wanted to save. Oct 8, 2013 at 15:54
  • @ChristofferHammarström very true Oct 9, 2013 at 9:39

4 Answers 4

9

1. These two tests return true:

# [ -d ] && echo true || echo false
true
# [ -d $SOME_UNSET_VAR ] && echo true || echo false
true

according to POSIX (as explained by @Tim).

2. But this returns false (not true as stated in the question)

# [ -d "" ] && echo true || echo false
false

because test is called with two arguments (although the second one is an empty string).

3. That's why it is good practice to use [[ … ]] instead of test ([ … ]), which most (all?) current shells provide. This construct checks if you supply enough arguments (otherwise throws an error and aborts)

# [[ -d ]] && echo true || echo false
bash: unexpected argument `]]' to conditional unary operator
bash: syntax error near `]]'

or simply behaves like one would expect:

# [[ -d $SOME_UNSET_VAR ]] && echo true || echo false
false

4. And, as pointed out by @Gilles, even more important is to double quote substitutions. So -d "$SOME_UNSET_VAR" expands to -d "" and returns false even with test (equal to case 2). Hence this is also compatible with the Bourne shell sh:

# [ -d "$SOME_UNSET_VAR" ] && echo true || echo false
false

tested with bash 3.00.16(1) and 4.1.5(1)

1
6

This question has already been answered on StackOverflow. It says that according to the POSIX standard, test should always return successful if it is called with exactly one non-empty argument (and no other arguments).

This should also be the case with test -e (and in fact it is on my system), so be careful.

Instead use:

[ -d "$ARTIFACTS" ]

test will then be called with two arguments even if the variable is empty and return false in this case.

4
  • "exactly one nonempty argument." - this is a misleading summary - the page you linked mentions being called with exactly one argument and that argument being non-empty; nothing about the case of a nonempty argument followed by an empty one. The correct solution should be to surround the variable name in quotes.
    – Random832
    Oct 8, 2013 at 13:28
  • @Random832 Thank you! I implemented both of your suggested changes.
    – Tim
    Oct 8, 2013 at 14:35
  • [ ! -z $ARTIFACTS ] && [ -d $ARTIFACTS ] is no better: it only caters for the particular case when $ARTIFACTS is empty, but fails when $ARTIFACTS may contain whitespace or \[?*. [ -d "$ARTIFACTS" ] is the correct way to do it (or [[ -d $ARTIFACTS ]] in shells that have [[ … ]]). Oct 8, 2013 at 17:16
  • @Gilles You are right, I removed it.Thank you!
    – Tim
    Oct 8, 2013 at 20:22
4

I solved the problem by using [ -e $ARTIFACTS ] which seems to work

You are wrong. It works because $ARTIFACTS is now set to something.

When a variable isn't set, then saying

[ -d $SOMEVAR ]

or

[ -e $SOMEVAR ]

would both evaluate to true because it implies saying

[ -d ]

and

[ -e ]

respectively. (Saying [ foobar ] would always evaluate to true.)

Saying

set -u

comes in handy in such situations. help set would tell you:

  -u  Treat unset variables as an error when substituting.
2
  • [ -e "" ] && echo "PRINT SOMETHING" does not print anything, so how can this be wrong? Oct 8, 2013 at 11:23
  • 2
    ahh crap [ -e $ARTIFACTS ] with empty artifacts yields [ -e ] not [ -e "" ], got it Oct 8, 2013 at 11:30
4

See that you set the variable ARTIFACTS and you were checking for ARTIFCATS. Probably mistyping?

Anyway, -d as well as -e would produce same results on unset variables.

Hence use double quotes and it will help you.

ARTIFACTS="/SOME/PATH"
[ -d "$ARTIFACTS" ] && rm -rf -- "$ARTIFACTS/"*

NOTE: If your "/SOME/PATH" has any folder with space, the script you mentioned will break with "binary operator expected" error.

Example:

ARTIFACTS="/home backup/"

1) [ -d $ARTIFACTS ] && rm -rf $ARTIFACTS/*
bash: [: /home: binary operator expected

2) [ -d "$ARTIFACTS" ] && rm -rf "$ARTIFACTS"/*

will do fine. Don't forget to put quotes in the rm invocation as well (rm -rf $ARTIFACTS would cheerfully remove /home then complain about backup/* not existing).

Also, including -L check will make sure that it is a directory and not just a symbolic link to a directory.

So, basically,

[ -d "$ARTIFACTS" && ! -L "$ARTIFACTS" ] && rm -rf -- "$ARTIFACTS"/*

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .