BASH file test operators with empty strings - bash

What do the BASH file test operators return when the path argument is an empty string? For example:
directory=""
# Something may or may not set "directory"
if [ -d "$directory" ]; then
# Do something...
fi

You need to quote $directory:
if [ -d "$directory" ]; then
Otherwise, after expansion you are left with the equivalent of
if [ -d ]; then
which is equivalent to if [ -n "-d" ]; then, and so is always true. (-d is a non-empty string.)

In your example, the statement will yield the following test:
if [ -d ]; then
...
fi
Which will not test what you wish to.
What is the accepted practice to prevent such error is to encase the variable in double quotes like this:
if [ -d "$directory" ]; then
...
fi

Related

Difference between `if [ ! -z "$var" ] then` and `if [ "$var"] then" in bash

Is there any difference between
if [ ! -z "$var" ] then
# do smth
fi
and
if [ "$var" ] then
# do smth
fi
They both seem to check if variable is set
Yes, they're equivalent, but there are a couple of notes that apply to both of them:
You need either a semicolon or a line break between ] and the then keyword, or it'll misparse them weirdly.
They're not testing whether the variable is set, they're testing whether it's set to something other than the empty string (see this question for ways to check whether a variable is truly unset).
However, I actually prefer a third also-equivalent option:
if [ -n "$var" ]; then
I consider this semantically clearer, because the -n operator specifically checks for something being non-empty.
There's no difference between
[ ! -z "$var" ]
[ -n "$var" ]
[ "$var" ]
All of them are true if $var is not empty.

Why does -d return true on empty variable

Assume I have not set the environment variable MY_DIR. Then I do this:
% if [ -d $MY_DIR ]; then
> echo WHAT?
> fi
WHAT?
I don't get it. Why does -d return TRUE if the environment variable doesn't exist?
Without quotes, word-splitting causes the empty string to disappear altogether, and the resulting command is [ -d ]. A single non-empty argument (] is ignored for the purpose of counting arguments) makes [ succeed. [ <word> ] is interpreted as [ -n <word> ], which tests that <word> isn't empty. [ -d ] is read as [ -n -d ] and always succeeds since -d isn't an empty string.
Use quotes.
if [ -d "$MY_DIR" ]; then # when unset, equivalent to [ -d "" ] with two arguments

Syntax error near unexpected token 'else'

I have looked at this for about 30 minutes now and can't seem to find the error in this. It happens at my if/else block at the end.
default()
{
for file in /*
do
if [ -f $file ]; then
((filecount++))
elif [ -d $file ]; then
((dircount++))
fi
done
echo The number of files is "$filecount"
echo The number of directories is "$dircount"
}
specific()
{
for file in $param
do
if [ -f $file ]; then
((filecount++))
elif [ -d $file ]; then
((dircount++))
fi
done
echo The number of files is "$filecount"
echo The number of directories is "$dircount"
}
#Variables
declare -a param=$1
declare -i filecount="0"
declare -i dircount="0"
#Action
if [ $param=='-h' ]; then
echo To use this program, enter a directory path after $0 or leave it blank to use current directory.
elif [ $param=='' ]; then
default()
else
specific()
fi
exit 0
Here is the error code. Any help is appreciated.
./countf.sh: line 44: syntax error near unexpected token `else'
./countf.sh: line 44: `else'
I checked your syntax only and found these errors.
function calls. As #Etan Reisner mentioned.
You need spaces around the comparison operator. like [ $param == '-h' ];
you need to double quote your variable. use [ "$param" == '-h' ] instead of [ $param == '-h' ] . Check this for more details. Double quote to prevent globbing and word splitting
I suggest you to test your script here. http://www.shellcheck.net/ I also should do that first instead of manually check your script.

Multiple `if` statements in bash script

I'm trying to write a short bash script that optionally accepts arguments from the command line, or prompts for their input
if [ [ -z "$message" ] && [ -z "$predefined" ] ] ; then
read -p "Enter message [$defaultMessage]: " message
message=${message:-$defaultMessage}
else
if [ -n "$predefined" ]; then
if [ -f $base/$environment/vle/data/$predefined.txt ]; then
echo Predefined message file $predefined.txt does not exist
exit 1
fi
fi
fi
If neither message nor predefined has been passed in as command line arguments, then the code should prompt for a value for message; otherwise if predefined has been passed in as a command line argument, then the script should test for the existence of a file of that name and only continue if the file does exist
But I'm getting the following error
[: -z: binary operator expected
at the first of those if tests
Any help in explaining what's wrong with my syntax for that first if statement? Or providing an alternative syntax to achieve the same objectives.
The first if is not well-formed. This would work:
if [ -z "$message" ] && [ -z "$predefined" ]; then
or this:
if test -z "$message" && test -z "$predefined"; then
or this bash-specific, easy but dirty way:
if [[ -z "$message" ]] && [[ -z "$predefined" ]]; then
or this bash-specific proper way:
if [[ -z $message && -z $predefined ]]; then
In this last version the double-quotes are unnecessary, not a typo.
Thanks #mklement0 for the corrections in the bash-specific style, and for this final comment:
I should note that there's one case where double-quoting is still a must inside [[ ... ]], namely if you want a variable reference on the right side of a string comparison (==) to be treated as a literal:
v='[a]'
[[ $v == $v ]] # FALSE!
[[ $v == "$v" ]] # true
Without double-quoting, the right-hand side is interpreted as a pattern. Some people advocate always double-quoting variable references so as not to have to remember such subtleties. That said (from bash 3.2 on), you must NOT double-quote the right operand when regex matching with =~
test expression1 -a expression2
is true if both expressions are true.
test expression1 -o expression2
is true if either or both expressions are true.
if [ -z "$message" -a -z "$predefined" ]; then
read -p "Enter message [$defaultMessage]: " message
message=${message:-$defaultMessage}
else
if [ -n "$predefined" -a -f $base/$environment/vle/data/$predefined.txt ]; then
echo Predefined message file $predefined.txt does not exist
exit 1
fi
fi
This was able to combine 4 test into 2 while also getting rid of one nested if expression; then ; fi

What's the difference between _string_ and -n _string_ in bash conditional expression?

The Bash Reference Manual says that
[ string ]
and
[ -n string ]
will both return true if the string 's length is not 0
but the fact is not as so
greet=
if [ $greet ]; then
echo '1'
else
echo '2'
fi
if [ -n $greet ]; then
echo '1'
else
echo '2'
fi
the output is
2
1
the Bash Reference Manual just says
-n string
string
True if the length of string is non-zero.
so, what the real difference between the two form?
As #user1502952 said, you need to use double-quotes; but let me explain why. Suppose you execute:
greet=
[ -n $greet ] && echo "it's nonblank"
When the shell parses the [ -n $greet ] part, it expands $greet to the empty string, and then does word splitting. For instance, if $greet expanded to something with spaces in the middle, it would treat each "word" as a separate argument to the [ command. In this case, however, $greet expands to nothing, which contains no "word"s at all, and hence is treated as zero arguments to [ -- it effectively vanishes from the command. So [ -n $greet ] is equivalent to [ -n ], which checks to see if the string "-n" is nonblank. It is, so it evaluates to true.
Compare this with [ -n "$greet" ]: in this case, the double-quotes allow the expansion of $greet, but prevent word splitting. So the [ command actually gets a zero-length second argument, realizes that -n is supposed to be an operator, and gets the expected answer.
when you are using -n option, it is required to use double quotes.
if [ -n "$greet" ]
as the string is empty the above expression evaluates to false, as the length is zero.
if [ "$greet" ]
this also evaluates to false as the string is empty.
Moreover to check for empty string, -z option can be used.
if [ -z "$greet" ]
this will be true as the string is empty.
Check this link too: http://tldp.org/LDP/abs/html/comparison-ops.html
Bash performs word splitting inside [ but not inside [[, so you don't have to quote parameters if you use [[:
$ x=
$ [ -n $x ]; echo $?; [ -n "$x" ]; echo $?
0
1
$ [[ -n $x ]]; echo $?; [[ -n "$x" ]]; echo $?
1
1
type shows [[ $x ]] as [[ -n $x ]]:
$ f() { [[ $x ]]; }; type f
f is a function
f ()
{
[[ -n $x ]]
}

Resources