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 ))
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,
In my Linux Mint 17.2 /etc/bash.bashrc I see the following:
# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
debian_chroot=$(cat /etc/debian_chroot)
fi
This is the first reference to the token debian_chroot.
Why does this code use ${debian_chroot:-} instead of just $debian_chroot?
Bash's Shell Parameter Expansion says:
${parameter:-word}
If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter is substituted.
Here, "word" is null, so why bother substituting null for null?
The syntax ${debian_chroot:-} prevents the shell from exiting if it is running with set -u (crash when using undefined variables) and debian_chroot is unset at that point.
You don't want a normal interactive shell to have set -u (it would crash too easily), but it can be very useful in scripts.
To see this:
bash -c 'set -u; [ -z $a ]; echo ok' # error
bash -c 'set -u; a=; [ -z $a ]; echo ok' # ok
bash -c 'set -u; [ -z ${a:-} ]; echo ok' # ok
bash -c 'set -u; a=; [ -z ${a:-} ]; echo ok' # ok
The use of the "${variable:-}" notation protects the script from an error if the shell is somehow invoked with -u or executes set -u — that causes a complaint when you use an undefined variable.
-u Treat unset variables and parameters other than the special parameters ‘#’ or ‘*’ as an error when performing parameter expansion. An error message will be written to the standard error, and a non-interactive shell will exit.
The purpose is to set a default value so that certain constructs in bash would not break.For example
Suppose var is unset, then :
if [ $var = "" ] #breaks => bash: [: =: unary operator expected
but
if [ "${var:-}" = "" ] # works fine
In case of
if [ -z "${debian_chroot:-}" ] # z checks if a SET variable is empty.
though, it doesn't make a difference and should work with just "${debian_chroot}" provided the -u bash option is not set.
[ bash reference ] says :
-u(nounset) Attempt to use undefined variable outputs error message, and forces an exit
Below script
#!/bin/bash -u
# note variable 'var' is unset
# Try the script as is and uncommenting the below line
#declare var=
if [ -z "${var}" ]
then
echo "var is empty"
fi
# "${var:-}" would work even if the 'var' is not 'declare'd.
would give you better idea.
I've been Bash scripting for a while and I'm wondering if there's any difference between these two forms of negation with the test command:
if [ ! condition ]; then
fi
if ! [ condition ]; then
fi
The first tells the shell to pass the arguments ! condition to test, letting the program take care of the negation itself. On the other hand, the second passes condition to test and lets the shell itself negate the error code.
Are there any pitfalls I should be aware of when choosing between these two forms? What values of $condition could make the results differ between them?
(I seem to remember reading an article a while ago discussing this, but I don't remember how to find it/exactly what was discussed.)
To build on chepner's insightful comment on the question:
In [ ! condition ], the ! is just an argument to the [ builtin (an effective alias of the test builtin); it so happens that [ / test interprets argument ! as negation.
In ! [ condition ], the ! is a shell keyword that negates whatever command it is followed by (which happens to be [ in this case).
One thing that the ! [ condition ] syntax implicitly gives you is that negation applies to whatever [ condition ] evaluates to as a whole, so if that is the intent, you needn't worry about operator precedence.
Performance-wise, which syntax you choose probably doesn't make much of a difference; quick tests suggest:
If condition is literally the same in both cases, passing the ! as an argument to [ is negligibly faster.
If ! is used as a keyword, and you are therefore able to simplify the condition by not worrying about precedence, it may be slightly faster (e.g, ! [ 0 -o 1 ] vs. [ ! \( 0 -o 1 \) ]; note that the POSIX spec. discourages use of -a and -o due to ambiguity).
That said, if we're talking about Bash, then you should consider using [[ instead of [, because [[ is a shell keyword that parses the enclosed expression in a special context that:
offers more features
allows you to safely combine expressions with && and ||
comes with fewer surprises
is also slightly faster (though that will rarely matter in pratice)
See this answer of mine.
! negates the exit code of following command :
$ ! test 1 = 1 || echo $? # negate command with true expression
1
As said in man page, test (and similar [ builtin) exit with the status determined by following expression :
$ test ! 1 = 1 || echo $? # return false expression
1
$ [ ! 1 = 1 ] || echo $?
1
For a given expression :
on one side you negate the test command that exit with true expression status.
on the other side, you negate an expression and the test command (or [) exit with its false status
Both will have the same effect.
So I would say that these syntax are equivalent. With the advantage for external ! to allow negate compound tests :
$ ! [ 1 = 1 -a 2 = 2 ] || echo $?
1
There is a difference if test/[ faces an error. Consider:
x=abc
if [ ! "$x" -gt 0 ]; then echo "first true"; fi
if ! [ "$x" -gt 0 ]; then echo "second true"; fi
The output is:
bash: [: abc: integer expression expected
second true
Unlike [[ .. ]], test/[ works like regular utility and signals errors by returning 2. That's a falsy value, and with ! outside the brackets, the shell inverts it just the same as a regular negative result from the test would be inverted.
With [[ .. ]] the behaviour is different, in that a syntax error in the condition is a syntax error for the shell, and the shell itself exits with an error:
if [[ a b ]]; then echo true; else echo false; fi
prints only
bash: conditional binary operator expected
bash: syntax error near `b'
with no output from the echos.
On the other hand, arithmetic tests work differently within [[ .. ]] so [[ "$x" -gt 0 ]] would never give an error.
I'm creating a basic script that should take 3 mandatory command line options and each one must be followed by a value. Like this:
$ myscript.sh -u <username> -p <password> -f <hosts.txt>
I'm trying to make sure the user is passing those exact 3 options and their values and nothing else, otherwise I want to print the usage message and exit.
I've been reading on getopts and came up with this:
usage () { echo "Usage : $0 -u <username> -p <password> -f <hostsFile>"; }
if [ $# -ne 6 ]
then
usage
exit 1
fi
while getopts u:p:f: opt ; do
case $opt in
u) USER_NAME=$OPTARG ;;
p) USER_PASSWORD=$OPTARG ;;
f) HOSTS_FILE=$OPTARG ;;
*) usage; exit 1;;
esac
done
echo "USERNAME: $USER_NAME"
echo "PASS: $USER_PASSWORD"
echo "FILE: $HOSTS_FILE"
I was hoping that if I do not pass any of my 3 "mandatory" options (i.e: -u -p -f) Optargs validation would catch that via the "*)" case. While that is true for other options such "-a","-b", etc.. does not seem to be the case in this particular case:
$ myscript.sh 1 2 3 4 5 6
Getops does not treat that as invalid input and the script moves on executing the echo commands showing 3 empty variables.
How can I capture the input above as being invalid as it is not in the form of:
$ myscript.sh -u <username> -p <password> -f <hosts.txt>
Thanks!
getopts has no concept of "mandatory" options. The colons in u:p:f: mean that, if one of those options happens to be supplied, then an argument to that option is mandatory. The option-argument pairs, however, are always optional.
You can require that the user provide all three though with code such as:
if [ ! "$USER_NAME" ] || [ ! "$USER_PASSWORD" ] || [ ! "$HOSTS_FILE" ]
then
usage
exit 1
fi
Place this code after the while getopts loop.
The Role of *)
I was hoping that if I do not pass any of my 3 "mandatory" options (i.e: -u -p -f) Optargs validation would catch that via the "*)" case.
The *) case is executed only if an option other than -u, -p, or -f is supplied. Thus, if someone supplied, for example a -z argument, then that case would run.
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.