Unix shell - file test operator without argument - bash

prompt> if [ -f ]; then echo "true"; fi
true
Why does the if condition evaluate to true? Isn't -f supposed to need an argument?
prompt> if [ -f $1 ]; then echo "true"; fi
Does it mean I always need to check if the argument $1 is not empty before proceeding to the if condition?

The reason [ -f ] results in a successful exit code is because of this bit from the manual:
-n string
string
True if the length of string is non-zero.
Which is also explained here when talking about test/[:
The test and [ builtins evaluate conditional expressions using a set
of rules based on the number of arguments.
0 arguments
The expression is false.
1 argument
The expression is true if and only if the argument is not null.
2 arguments
If the first argument is ‘!’, the expression is true if and only if the second argument is null. If the first argument is one of the unary conditional operators (see Bash Conditional Expressions), the expression is true if the unary test is true. If the first argument is not a valid unary operator, the expression is false.
3 arguments
...
4 arguments
...
5 or more arguments
...
So while you expect to be using the two argument test the shell sees the one argument version and tests the string -f against the null string.
That is [ word ] is equivalent to [ -n word ].
You can avoid this problem by quoting your variable in the test:
if [ -f "$1" ]; then echo true; fi
In general you want to always quote variable expansions.

You can avoid that scenario using if [[ ]] syntax or simply by quoting the variable $1 in your current code.
Example:
if [ -f "$1" ]; then echo "true"; fi
Or:
if [[ -f $1 ]]; then echo "true"; fi
Note: Always try to quote variables.

Related

Is the bash double bracket conditional expression without operators, specified/documented anywhere?

[[ $VAR ]] appears to be equivalent to [[ -n $VAR ]]
The bash manual says:
Expressions may be unary or binary
which implies an operator is required, but it's not.
They're listed as equivalent in the Conditional Expressions section of the bash man page:
string
-n string
    True if the length of string is non-zero.
This is also implied by the POSIX spec for the test command (which is equivalent to [ ], which the [[ ]] syntax is based on):
In the following list, $1, $2, $3, and $4 represent the arguments presented to test:
0 arguments:
    Exit false (1).
1 argument:
    Exit true (0) if $1 is not null; otherwise, exit false.
Therefore [ ] and [ '' ] exit with false/failure status, but [ something_nonblank ] exits with true/success status.

Bash why quotes in while cycles make different behaviour?

I wanted to read an input $1 using parameters. I used this code:
while [ -n "$1" ]
do
case $1 in
-dt) do something;;
esac
shift
done
Why if I remove those quotes in the while instruction the script is stuck in a permanent loop? Quotes should let you evaluate a string making special characters like normal characters but I'm not inserting any special character in the input and anyway it has a different behavior with or without quotes
Without the quotes, when $1 is empty, [ -n $1 ] will become [ -n ] which means [ -n "-n" ] which is TRUE.
According to bash manual:
test and [ evaluate conditional expressions using a set of rules
based on the number of arguments.
0 arguments
The expression is false.
1 argument
The expression is true if and only if the argument is not null.
[...]

Argument isn't passed (bash)

I'm working with shell scripts.
I'm in the test section, where if an argument is passed:
The expression is true if, and only if, the argument is not null
And here I have implemented the following code:
[ -z $num ]; echo $?;
Your exit:
0
Why?
Firstly, [-z should be [ -z, otherwise you would be getting an error like [-z: command not found. I guess that was just a typo in your question.
It sounds like you're quoting the wrong part of the manual, which would apply to tests like this:
[ string ] # which is equivalent to
[ -n string ]
Either of which would return success (a 0) for a non-empty string.
With -z, you're checking that the length of the string is 0.
However, as always, be careful with unquoted variables, since:
[ -z $num ]
# expands to
[ -z ]
# which is interpreted in the same way as
[ string ]
i.e. your test becomes "is -z a non-empty string?", to which the answer is yes, so the test returns 0. If you use quotes around "$num" then the test does what you would expect.

Bash: `if ! [ $falseSetVar ] ` won't evaluate correctly for me

I have an if statement within a loop. It's set to false initially so I insert a timestamp in a file at the first run of the loop.
I can't seem to get the following to evaluate correctly.
$ConnectionIsCurrently=false
if ! [ $ConnectionIsCurrently ]; then
# changing false to true so this only occurs once.
$ConnectionIsCurrently=true
fi
Here is the full loop:
while [ $i -le $NoOfTests ]; do
ping -c1 -t1 www.google.ie > /dev/null
if [ $? = 0 ]; then
ConTestPASSCount=$((ConTestPASSCount+1))
if ! [ $ConnectionIsCurrently ]; then
printf 'PASSED AT: '
date "+%s"
printf 'PASSED AT: ' >> $directory$LogFile
date "+%s" >> $directory$LogFile
ConnectionIsCurrently=true
fi
echo "PASSCount $ConTestPASSCount"
else
ConTestFAILCount=$((ConTestFAILCount+1))
if [ $ConnectionIsCurrently ]; then
printf 'FAILED AT: '
date "+%s"
printf 'FAILED AT: ' >> $directory$LogFile
date "+%s" >> $directory$LogFile
ConnectionIsCurrently=false
fi
echo "FAILCount $ConTestFAILCount"
fi
sleep 1
Testcount=$((Testcount+1))
i=$((i+1))
done
The shell doesn't have boolean values, it just operates on strings (or numbers in $(())). The syntax:
if [ $ConnectionIsCurrently ]
tests whether $ConnectionIsCurrently is a non-empty string, and "false" is not empty.
You could use an empty value as falsey, and any non-empty value as truthy.
ConnectionIsCurrently=
if ! [ "$ConnectionIsCurrently" ]; then
ConnectionIsCurrently=true
fi
Note also that you don't put $ before the variable name when you're assigning to it, only when you're reading it. And you should generally quote variables, unless you're sure you want word splitting done. This is especially important when the variable could be empty, as in this case; without the quotes, the [ command doesn't receive any parameter there.
false and true are actually commands (and also bash builtins), so you can run them as commands and act on the exit status:
ConnectionIsCurrently=false
if ! $ConnectionIsCurrently; then
# changing false to true so this only occurs once.
ConnectionIsCurrently=true
fi
The [...] are not required syntax for the if command: [ is just a regular command whose exit status is used by if.
To summarize:
if and while execute a command and branch depending on whether that command succeeds or fails.
false is a command that produces no output and always fails.
true is a command that produces no output and always succeeds.
[ is a command that succeeds or fails depending on the evaluation of the expression preceding the closing ] argument; man test or info test for details. With a single argument (which should be enclosed in double quotes) before the ], [ succeeds if and only if the argument is non-empty. The [ command is typically built into the shell, but it acts like a command; it's not a special shell syntax.
The shell (sh, bash, ksh, zsh) does not have built-in Boolean types or values. There are several common idioms for using Booleans in shell scripts.
A. Assign a variable the string value true or false. Using such a value in an if statement will do the right thing. (This method is my personal favorite.) Note that the strings true and false are the names of commands, not arbitrary strings.
foo=true
if $foo ; then echo OK ; else echo Oops ; fi
B. Assign a variable any arbitrary non-empty value for truthiness, or the empty string (or leave it unset) for falsitude:
foo=yes
if [ "$foo" ] ; then echo OK ; else echo Oops ; fi
foo=""
if [ "$foo" ] ; then echo Oops ; else echo OK ; fi
(The shell treats an unset variable as if it were set to the empty string -- unless you've done set -o nounset, but that's not usually done in scripts.)
C. Pick two arbitrary strings to represent truth and falsehood, and use them consistently. Use string comparisons to test.
foo=TRUE
if [ "$foo" = TRUE ] ; then echo OK ; else echo Oops ; fi
foo=FALSE
if [ "$foo" = TRUE ] ; then echo Oops ; else echo OK ; fi
All of these methods are potentially error-prone. If you forget a $ or misspell one of your conventional strings, you can get bad results with no warning from the shell; for example with method C, the string True will silently be treated as a false condition. Languages with strictly behaving Booleans can avoid these problems. Bash is not such a language.

Testing against -n option in BASH scripts always returns true

I am writing a bash script, in which I am trying to check if there are particular parameters provided. I've noticed a strange (at least for me) behavior of [ -n arg ] test. For the following script:
#!/bin/bash
if [ -n $1 ]; then
echo "The 1st argument is of NON ZERO length"
fi
if [ -z $1 ]; then
echo "The 1st argument is of ZERO length"
fi
I am getting results as follows:
with no parameters:
xylodev#ubuntu:~$ ./my-bash-script.sh
The 1st argument is of NON ZERO length
The 1st argument is of ZERO length
with parameters:
xylodev#ubuntu:~$ ./my-bash-script.sh foobar
The 1st argument is of NON ZERO length
I've already found out that enclosing $1 in double quotes gives me the results as expected, but I still wonder why both tests return true when quotes are not used and the script is called with no parameters? It seems that $1 is null then, so [ -n $1 ] should return false, shouldn't it?
Quote it.
if [ -n "$1" ]; then
Without the quotes, if $1 is empty, you execute [ -n ], which is true*, and if $1 is not empty, then it's obviously true.
* If you give [ a single argument (excluding ]), it is always true. (Incidentally, this is a pitfall that many new users fall into when they expect [ 0 ] to be false). In this case, the single string is -n.

Resources