A case in which both test -n and test -z are true - linux

#! /bin/bash
echo "Please input 2 nums: "
read a b
if [ -z $b ]; then
echo b is zero !
fi
if [ -n $b ]; then
echo b is non-zero !
fi
when run the script, only input 1 number, and leave the other empty, then b is supposed to be null. but the result is both echo is printed.
-laptop:~$ ./test.sh
Pleaes input 2 nums:
5
b is zero !
b is non-zero !
b is both null and non-null ?! Could anyone comment on this ? Thanks !
~

Replace
if [ -z $b ]; then
with
if [ -z "$b" ]; then
And do the same in the other if condition as well.
See http://tldp.org/LDP/abs/html/testconstructs.html for some interesting tests.

It's all in the quotes. I don't remember where, but someone explained this recently on SO or USE - Without the quotes it doesn't actually do an empty/non-empty string test, but just checks that -n or -z are non-empty strings themselves. It's the same test that makes this possible:
$ var=-n
$ if [ "$var" ]
then
echo whut
fi
Returns whut.
This means you can also have a sort of functional programming:
$ custom_test() {
if [ "$1" "$2" ]
then
echo true
else
echo false
fi
}
$ custom_test -z "$USER"
false
$ custom_test -n "$USER"
true

The -n test requires that the string be quoted within the test brackets. Using an unquoted string with ! -z, or even just the unquoted string alone within test brackets normally works, however, this is an unsafe practice. Always quote a tested string.
$ b=''
$ [ -z $b ] && echo YES # after expansion: `[ -z ] && echo YES` <==> `test -z && echo YES`
YES
$ [ -n $b ] && echo YES # after expansion: `[ -n ] && echo YES` <==> `test -n && echo YES`
YES
test against nothing, yield true.

Related

Options on bash script (completely new to this)

So I'm beginning making bash scripts. I can do basic stuff, but that's it.
I want to make something so when I type in:
./myprogram -t
It will do "echo true"
And if I type in:
./myprogram -f
It will do "echo false"
Thanks in advance
The positional parameters are available through the variables $1 $2 etc.
There are many ways to implement the contition. You could use an if statement:
#!/bin/bash
if [ "$1" = -t ]
then
echo true
elif [ "$1" = -f ]
then
echo false
fi
A case statement:
#!/bin/bash
case "$1" in
-t) echo true ;;
-f) echo false ;;
esac
Or a short-circuit:
#!/bin/bash
[ "$1" = -t ] && echo true
[ "$1" = -f ] && echo false
For more complex cases consider using the getopt or the getopts libraries.
The word for what you are calling an "option" is typically referred to as an argument in programming. You should read more about how to handle arguments in bash by reading everything at http://tldp.org/LDP/abs/html/othertypesv.html . To answer your direct question the script might look like this:
#!/bin/bash
if [[ $# -eq 0 ]]; then
echo 'No Arguments'
exit 0
fi
if [ $1 = "-f" ]; then
echo false
elif [ $1 = "-t" ]; then
echo true
fi

KSH scripting: -z and -a

I have the below condition:
if [ ! -z $DateC -a "$DateC"=="$DateJ" ]
If had like to know what -z and -a means. I don't understand what this condition verifies. I have searched the net for -a and -z but I don't really get what it does. Any help, pls?
Check man test for this:
-z STRING
the length of STRING is zero
EXPRESSION1 -a EXPRESSION2
both EXPRESSION1 and EXPRESSION2 are true
So this checks if the length of $DateC is not zero and $DateC and $DateJ are equal.
if [ ! -z $DateC -a "$DateC"=="$DateJ" ]
# ^^^^^^^^^ ^^
# | AND
# |
# length of string is zero
#
# ^^^^^^^^^^^^^^^^^^^^^^^^^^
# length of string is NOT zero
See an example:
$ r=hello
$ s=hello
$ [ "$r"=="$s" ] && echo "yes" || echo "no"
yes
With the other condition:
$ t=""
$ if [ ! -z "$t" -a "$r"=="$s" ]; then echo "yes"; else echo "no"; fi
no
$ t=a
$ if [ ! -z "$t" -a "$r"=="$s" ]; then echo "yes"; else echo "no"; fi
yes
Finally,
Note that POSIX recommends the use of && and || with the
single bracket notation over -a and -o, so if you are writing
portable code go for the first notation, otherwise the second and skip
the third, especially since it can get awkward and hard to read if you
need to group expressions. – Adrian Frühwirth (source)

Grep for a dollar sign within backticks

I have a file like this
File name : hello.txt
{1:ABC}{2:BCD}{3:{108:20140619-2}}{4:
:97A::Hi//12345
:97A::Hi//12345
:93B::Hello//FAMT/00000,
:16S:FIN
-}{5:{CHK:BDG6789}}{S:{ABC:}{DEF:S}{WOM:ONHGRT}}
Now basically i'm checking for the existence of $ symbol and as well as :97A: AND im using the below if statement.
if [ `grep -c '\$' hello.txt` -gt 0 ] && [ `grep -c ":97A:" hello.txt` -gt 1 ]
then
echo "condition satisfied"
else
echo "condition not satisfied"
fi
if i execute this im getting condition satisifed echo statement. But id should be the other way round :( since im putting AND condition. Please help on this.
I also don't understand what you're asking, but from your code I conclude that you have troubles grepping for the dollar sign. I guess you need to escape the backslash as well if you use backticks:
$ echo 'foo$bar' > dollar.txt
$ echo 'foo_bar' > no_dollar.txt
$ [ `grep -c '\$' dollar.txt` -gt 0 ] && echo 1
1
$ [ `grep -c '\$' no_dollar.txt` -gt 0 ] && echo 1
1
$ [ `grep -c '\\$' dollar.txt` -gt 0 ] && echo 1
1
$ [ `grep -c '\\$' no_dollar.txt` -gt 0 ] && echo 1
$ [ `grep -c '\\$' no_dollar.txt` -gt 0 ] || echo 0
0
alternatively, use $() instead of backticks
Replace grep -c '\$' hello.txt by grep -c '\\$' hello.txt Then it will work as desired.
ex:
bash -x test.sh
++ grep -c '\$' hello.txt
+ '[' 0 -gt 0 ']'
+ echo 'condition not satisfied'
condition not satisfied
PS: bash -x is your friend :)
I recommend executing your grep commands in a subshell with the $ syntax, then doing the comparison. In my opinion this is the cleaner way and requires you to be less of an escape artist.
if [ $(grep -c '\$' hello.txt) -gt 0 ] && [ $(grep -c ":97A:" hello.txt) -gt 1 ]
then
echo "condition satisfied"
else
echo "condition not satisfied"
fi
For your hello.txt the output will be:
>> bash test.bash
condition not satisfied
Since there's no dollar sign in your file
[ $(grep -c '\$' hello.txt) -gt 0 ]
will test
[ 0 -gt 0 ]
and yield false, while
[ $(grep -c ':97A' hello.txt) -gt 1 ]
will test
[ 2 -gt 1 ]
and yield true. Finally, false && true will yield false and the second echo statement will be executed.
"i'm checking for the existence of $ symbol"
First condition won't match because there is no "$" sign anywhere in your input, therefore output of first grep is 0. As 0 isn't greater than 0, the result is "false". Consequently, the second clause won't be executed at all. "Condition is not satisfied" because your requirement for "satisfied" is: input contains both "$" AND ":97A:".
For a result whether grep matched any line, you don't need to count number of matches.
if grep -q '\$' file; then ...
is a way to use result of grep in a conditional statement without rube-goldbergismns
Using awk and reading the file only once:
if awk '/[$]/{d++} /:97A:/{o++} END{exit !(d && o>1)}' hello.txt; then
echo "condition satisfied"
else
echo "condition not satisfied"
fi

Bash null binary operators

A recent test I took had a question on the output of the following bash command:
var=; [ -n $var ]; echo $?; [ -z $var ]; echo $?
The results are 0 and 0, indicating the return codes for both unary operators had no errors. This means $var resolves to both null (empty) and 'non-null' (not empty), correct?
How is this possible?
No, it means that [ is unfixably broken. In both cases $var evaluates to nothing, and the commands simply execute [ -n ] and [ -z ] respectively, both of which result in true. If you want to test the value in the variable itself then you must quote it to have it handled properly.
$ var=; [ -n "$var" ]; echo $?; [ -z "$var" ]; echo $?
1
0
You will need to surround $var:
$ [ -n "$var" ]; echo $?
1
Remember that the closing square bracket is just syntactic sugar: you don't need it. That means your line:
$ [ -n $var ]; echo $?
will expand to (since $var is empty):
$ [ -n ]; echo $?
The above asks: "is the string ']' non-empty?" And the answer is yes.
It's surprising indeed. If you were to try the same with the bashism [[ syntax, you'd get 1 and 0 as results. I reckon this is a bug.
var=; [[ -n $var ]]; echo $?; [[ -z $var ]]; echo $?
or, as Ignacio points out and as in fact I have always been doing intuitively, with defensive coding and quoting:
var=; [[ -n "$var" ]]; echo $?; [[ -z "$var" ]]; echo $?
It's surprising to me that [ behaves this way, because it's a builtin:
$ type [
[ is a shell builtin
Just did a little test and the system command [ behaves in the same broken way as the builtin. So probably it's buggy for compatibility:
var=; /usr/bin/\[ -n $var ]; echo $?; /usr/bin/\[ -z $var ]; echo $?

When should you push variables in quotes in boolean conditions?

Are the following two boolean expressions the same?
if [ -n $1 ] ; then
if [ -n "$1" ] ; then
And these two
if [ $? == 0 ] ; then
if [ "$?" == 0 ] ; then
If not - When should you put a variable in quotes?
put variables in quotes when there is any chance that the value may contain white spaces, or in general NOT be a continuous string of characters. So
as $? should always be something between 0 and 255, you don't need to quote that because it is a return value set after each sub-process returns. It is NOT possible to break that by assigning a string value directly, i.e.
$?=Is of course wrong and should be
?=Bad value assigment
because a user variable name must start with [A-Za-z_] , so don't do that ;-)
Whereas for $1, if a value is passed in like
myscript "arg1 with spaces"
the test
if [ -n $1 ] ; then
will blow up,
but the test
if [ -n "$1" ] ; then
will succeed.
IHTH
Using the [ command (yes, it's a command), you should nearly always use quotes. The exceptions are so rare I may not even go into them. For example,
set -- 'x -a -z b'
if [ -n $1 ]; then echo foo; fi
# … crickets
if [ -n "$1" ]; then echo foo; fi
foo
Bash also has the [[ keyword (yes, it's a keyword), which is smart enough to know about expansions and avoid the traps of not quoting. (But it's still usually safe to quote within [[ expressions.)
Since the [ command is POSIX compliant and [[ is a bashism, you decide when to use which.
As for your second example, if you're comparing something to a number, using == is a bashism. If you're writing for bash, use an arithmetic expression, such as
if (( $? == 0 )); then …
But if you're trying for POSIX compliance, write it
if [ "$?" = 0 ]; then …
Often, direct comparisons against $? are a red flag, since comparing the success/failure of a command is exactly what if does:
if somecommand; then …
is better than
somecommand
if (( $? == 0 )); then …
But if you do need to use it, $? is something of an exception, because it is guaranteed to only ever be a single-byte unsigned integer, so it's pretty safe to use unquoted, although it doesn't hurt to quote.
$ false; if [ $? = 0 ]; then echo t; else echo f; fi
f
$ true; if [ $? = 0 ]; then echo t; else echo f; fi
t
$ false; if [ "$?" = 0 ]; then echo t; else echo f; fi
f
$ true; if [ "$?" = 0 ]; then echo t; else echo f; fi
t
$ false; if [ $? == 0 ]; then echo t; else echo f; fi
f
$ true; if [ $? == 0 ]; then echo t; else echo f; fi
t
$ false; if [ "$?" == 0 ]; then echo t; else echo f; fi
f
$ true; if [ "$?" == 0 ]; then echo t; else echo f; fi
t
They are not always the same. If the variable in question is empty or contains whitespace, the [ command (alias for test) can have too few or too many arguments.
In bash, you can use the builtin [[ -n $1 ]] condition construct that does not split the variable into words before evaluating the condition, which means no double quotes are needed.

Resources