What environment variable or something internally the 'if' keyword checks to decide true/false.
I am having something like below two statements. abc is mounted , but not pqr.
if mount |grep -q "abc"; then echo "export pqr"; fi
if mount |grep -q "pqr"; then echo "export abc"; fi
In the above case I expected first statement to do nothing since abc is mounted(so finds the row in mount o/p) hence the $? after mount |grep -q "abc" is 0.
And I expected second statement to execute the echo. But it's happening otherwise, first statement is printing but second not. So want to understand on what basis if decides true/false.
Here is one related question
But the accepted answer for that question says :
if [ 0 ]
is equivalent to
if [ 1 ]
If this is true then both my statements should do echo, right?
There is a basic difference between the commands that you are issuing and the analogy that you are drawing from the referenced question.
When grep is executed with the -q option, it exits with a return code of zero is the match is found. This implies that if the output of mount were to contain abc, then
if mount |grep -q "abc"; then echo "export pqr"; fi
is equivalent to saying:
if true; then echo "export pqr"; fi
Note that there is no test command, i.e. [, that comes into the picture here.
Quoting from the manual:
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.
This explains why [ 0 ] and [ 1 ] both evaluate to true.
The if command does not act, like C-like languages, on the "boolean value" of an integer: it acts on the exit status of the command that follows. In shell, an exit status of 0 is considered to be success, any other exit status is failure. If the command following if exits with status 0, that is "true"
Example:
$ test -f /etc/passwd; echo $?
0
$ test -f /etc/doesnotexist; echo $?
1
$ if test -f /etc/passwd; then echo exists; else echo does not exist; fi
exists
$ if test -f /etc/doesnotexist; then echo exists; else echo does not exist; fi
does not exist
Note that [ and [[ are (basically) commands that (basically) alias test
if [ 0 ]
tests to see if the string 0 is non-empty. It is non-empty, so the test succeeds. Similarly, if [ 1 ] succeeds because the string 1 is non-empty. The [ command (also named test) is returning a value based on its arguments. Similarly, grep returns a value.
The if keyword causes the shell to execute commands based on the value returned by the command, but the output of the command preceded by if is irrelevant.
The command test 0 (equivalent to the command [ 0 ] returns a value of 0. The command test 1 also returns a value of 0. Zero is treated by the shell as a success, so the commands of the if clause are executed.
Related
I am trying to understand the following code:
if [ -z "$1" ] || [ -z "$2" || [ "${3:-}" ]
then
echo "Usage: $0 <username> <password>" >&2
exit 1
fi
I want to understand what we mean by -z "$1" and "${3:-}" in the code.
Please also help me understand >&2 in the code.
1) Your code is not correct, you missed one ] bracket somewhere. Probably after [ -z "$2" block.
2) if statement executes following command(s) and then executes block of code enclosed in then .. fi or then .. else keywords if the return value of the command(s) is true (their exit code is 0)
3) [ is just an alias for the test command (try man test). This command takes several parameters and evaluates them. For example, used with -z "$something" flags would return true (0) if $something is not set or is an empty string. Try it:
if [ -z "$variable" ]; then
echo Variable is not set or is an empty string
fi
4) || statement is an OR. Next command would be executed if the previous one returned false statement. So in the statement
if [ -z "$variable" ] || [ -z "$variable2" ]; then
echo Variable 1 or variable 2 is not set or is an empty string
fi
command [ -z "$variable2" ] would be executed only if variable was empty. The same could be achieved with different syntax:
if [ -z "$variable" -o -z "$variable2" ]; then
echo Variable 1 or variable 2 is not set or is an empty string
fi
which should be faster, because it requires only one instance of the test program to be run. Flag -o means OR, so you could read it as:
If variable is not set/empty OR variable2 is not set/EMPTY...
5) Statement "[ ${3:-} ]" means return true if $3 (the third argument of the script) is set.
6) >&2 is a stream redirection. Every process has two outputs: standard output and error output. These are independent and could be redirected (for example) to be written to two different files. >&2 means "redirect standard output to the same location as standard error".
So to sum up: commands between then .. fi will be executed IF the script is run with $1 empty or $2 empty or $3 NOT empty That means that the script should be run with exactly two parameters. And if not, the echo message will be printed to standard error output.
-z STRING means the length of STRING is zero.
${parameter:-word} If parameter is unset or null, the expansion of word is substituted. In your case $3 is just set with a blank value, if $3 do not have any value.
&2 writes to standard-error. I mean the stdout value of the executed command is sent to stderr,
(echo "123" | grep "xyz" > /dev/null) && echo $
(echo "123" | grep "123" > /dev/null) && echo $?
Command1 hasn't any output.
Command2 output '0'.
See the help of grep.
EXIT STATUS
The grep utility exits with one of the following values:
0 One or more lines were selected.
1 No lines were selected.
>1 An error occurred.
I feel confused about "expression1 && expression2".
If expression1 is true , expression2 will be executed.Is that right?
If grep matched "123", it will return zero(0 equal to false).If return zero, expression1 will be false.Why echo $? has been executed when expression1 is false?
Your presumption that 0 is equal to false is not correct. It's the opposite. 0 means that a command executed successfully, just as the help for grep says. Therefore, any non-zero exit status is considered an error, hence false. This is also the case for the test command commonly used in if statements.
With this in mind, the result of this script is as expected.
I found some strange thing in bash and I can't understand how it works.
[test ~]$ a=""
[test ~]$ $a && echo 1
1
[test ~]$ $a
[test ~]$ echo $?
0
Why does $a (which is empty) return 0? Is it somehow transformed to empty command?
If I add quotes or write empty string before &&, it will return error. While empty command returns 0.
[test ~]$ "$a" && echo 1
-bash: : command not found
[test ~]$ "" && echo 1
-bash: : command not found
[test ~]$ `` && echo 1
1
So, what is happening when I type $a?
You seem to confuse bash with some other programming language. Variables get replaced, then what is left gets executed.
"$a"
This is the content of a, between quotation marks. a is empty, so this is equivalent to:
""
That is not a command. "Command not found." As there was an error, the execution was not successful (shell return code is not 0), so the second half of the command -- && echo 1 -- does not get executed.
Backticks...
``
...execute whatever is between them, with the output of that command replacing the whole construct. (There is also $() which does the same, and is less prone to being overlooked in a script.) So...
`echo "foo"`
...would evaluate to...
foo
...which would then be executed. So your...
``
...evaluates to...
<empty>
...which is then "executed successfully" (since there is no error).
If you want to test the contents of a, and execute echo 1 only if a is not empty, you should use the test command:
test -n "$a" && echo 1
There is a convenient alias for test, which is [, which also conveniently ignores a trailing ]...
[ -n "$a" ] && echo 1
...and a bash-ism [[ that "knows" about variable replacement and thus does not need quotation marks to avoid complaining about a missing argument if $a does indeed evaluate to empty...
[[ -n $a ]] && echo 1
...or, of course, the more verbose...
if [[ -n $a ]]
then
echo 1
fi
Ah. Missed the core part of the question:
$a && echo 1
This is two statements, separated by &&. The second statement only gets executed if the first one executes OK. The bash takes the line apart and executes the first statement:
$a
This is...
<empty>
...which is "successful", so the second statement gets executed. Opposed to that...
&& echo 1
...is a syntax error because there is no first statement. ;-) (Tricky, I know, but that's the way this cookie crumbles.)
a=""
or
a=" " #a long empty string
then
$> $a
will return 0
$> $noExistVar
will also return 0.
They get "executed", in fact, nothing gets executed. same as you press enter or pressing 10 spaces then enter, you get return code 0 too.
$> && echo 1
this will fail, because bash will try to execute the first part, in this case it is missing.
$> $notExistVar && echo 1
Here it works, I guess bash found the first part the $whatever, therefore no syntax error. Then "execute" it, well nothing to execute, return 0, (same as pressing enter after prompt), then check, if first part returned 0, exec the cmd after &&.
I said guess because I didn't check bash's source codes. Please correct me if it is wrong.
the $> " " && echo 1 case, I think it is clear, don't need to explain.
I am using a simple bash function which executes a dbus command and retrieve its result.
getValue()
{
local -i val
declare -a array
array=($(dbus-send --system --print-reply ...))
val=${array[7]}
echo $val
unset array
if [ ! -z ${val} ]
then
...
fi
}
While the dbus command return an expected value the function works fine. However, when the dbus is in a bad state :
Error org.freedesktop.DBus.Error.NoReply: Did not receive a reply. Possible causes include: ...
the "echo $val" returns 0 and the condition if [ ! -z "$val" ] is satisfied. How to avoid this incorrect state ?
Assuming that when the error is produced, the exit status of dbus is non-zero (failure), you can add another check:
array=($(dbus-send --system --print-reply ...))
val=${array[7]}
if [ $? -eq 0 ] && [ -n "$val" ]
$? contains the return code of the last command to be executed (dbus in this case), which is usually 0 for success. I changed your condition to -n to check for non-empty and quoted $val as it is generally considered good practice. The curly braces you used don't offer any protection against glob expansion, e.g. if $val contained a *.
You can redirect the standard error of your command to join up with the standard output, e.g.,
array=($(dbus-send --system --print-reply ... 2>&1 ))
I have a sample shell method which:
Either returns a value after some processing.
Else exit with an exit code if any error occurs.
The sample script is as follows:
a.sh
#!/bin/bash
test(){ # test method
if [ $1 == 2 ]; then # the condition for checking
exit 500 # exit with exit code
else
echo $1 # do some business logic here and return the value
fi
}
I have problem with checking the error code. To use this test method I have another sample script.
b.sh
#!/bin/bash
source a.sh
val=`test $1` # call the test method
if [ $? == 500 ]; then # check the value
echo "here is an error" # error occurs
else
echo $val # no error, do something with returned value
fi
Followings are the output:
Input: ./b.sh 10
Output: 10
Expected output: 10
Input: ./b.sh 2
Output:
Expected output: here is an error
I think there is the problem in b.sh because if [ $? == 500 ]; then is always false. Is there any way to make this condition true or something to get the error code?
The idiomatic way of doing this is:
if val=$(test "$1"); then
echo "$val"
else
echo "An error occurred."
fi
The if statement tests the status of the command (or pipeline) which follows it, and executes the then branch of the status indicated success. The only time you need to explicitly check the value of $? is the rare case of a utility which returns different failure status values (and documents what they mean).
Many people seem to think that [ ... ] and other such things are part of the syntax of the if command. They are not; they are just commands (or builtins) whose names are punctuation. You can use any command whatsoever, or even several in a row; in the latter case, the status checked will be that of the last command.