Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

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

share|improve this question
5  
Or maybe you executed both lines. In the code example above, you never set ARTIFCATS. –  Buhb Oct 8 '13 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. –  Christoffer Hammarström Oct 8 '13 at 15:54
    
@ChristofferHammarström very true –  Moataz Elmasry Oct 9 '13 at 9:39

4 Answers 4

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)

share|improve this answer
1  
Bash, ksh and zsh provide [[ … ]] but not plain sh. The really important practice is to put double quotes around command substitutions: [ -d "$ARTIFACTS" ]. –  Gilles Oct 8 '13 at 17:15

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.

share|improve this answer
    
"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 '13 at 13:28
    
@Random832 Thank you! I implemented both of your suggested changes. –  Tim Oct 8 '13 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 [[ … ]]). –  Gilles Oct 8 '13 at 17:16
    
@Gilles You are right, I removed it.Thank you! –  Tim Oct 8 '13 at 20:22

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.
share|improve this answer
    
[ -e "" ] && echo "PRINT SOMETHING" does not print anything, so how can this be wrong? –  Moataz Elmasry Oct 8 '13 at 11:23
2  
ahh crap [ -e $ARTIFACTS ] with empty artifacts yields [ -e ] not [ -e "" ], got it –  Moataz Elmasry Oct 8 '13 at 11:30

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"/*
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.