using if and a boolean function in bash script: if condition evaluates to false when function returns true - bash

is_dir_empty(){
for file in "$1"
do
if [ "$file" != "$1" ]; then
return 0
fi
done
echo "return 1"
return 1
}
file="/home/tmp/*.sh"
if is_dir_empty "$file"; then
echo "empty"
else echo "not empty"
fi
it outputs
return 1
not empty
so is_dir_empty returned 1 but if condition evaluated to false somehow.... why?

Because shell scripts follow the Unix convention of expecting utilities to return zero for success and non-zero for failure, so boolean conditions are inverted.

Globs are not expanded in double quotes, so you're always comparing against the literal value /home/tmp/*.sh. Unquote $1 in the for loop, and it'll word split and glob expand into a list of .sh files (this online tool would have pointed this out automatically).
Also, unlike in C, zero is considered success and non-zero failure.

you can check if a directory is empty by:
[ "$(ls -A /path/to/directory)" ] && echo "Not Empty" || echo "Empty"

Related

bash if "$1" == "0" is always false when running function for bash prompt

I have been struggling with this for a long time.
Trying to change colour as part of my prompt depending on the exit code of the last command.
I have reduced my prompt to a minimal example:
Red="\[\033[31m\]"
Green="\[\033[32m\]"
Reset="\[\033[0m\]"
statColour(){
if [[ "$1" == "0" ]]; then
echo -e "${Green} $1 "
else
echo -e "${Red} $1 "
fi
}
export PS1="$(statColour \$?)What Colour? $Reset"
And results in red always being used despite the fact the number is clearly 0 in the first instance.
I have tried [ and $1 -eq 0 with no success. Why isn't this working?
Try this:
Red="\033[35m"
Green="\033[32m"
Reset="\033[0m"
statColour(){
if [[ $1 = 0 ]]; then
echo -e "${Green} $1 "
else
echo -e "${Red} $1 "
fi
}
export PS1="\$(statColour \$?)What Colour? $Reset"
# ^
Color definitions changed
Call of statColour is now done every time, and not only once.
if [[ ]] optimized
For an explanation why you always take the false branch:
You are calling statColour with \$? as argument. The backslash ensures, that the $ is taken literally (and not as the start of a parameter expanson), so you have in effect the literal string $?. Since ? is a wildcard character, it is undergoing filename generation, i.e. the parameter is replaced by all files where the name is a $, followed by a single character. If there are no such files in your directory (which is probably the case), the string $? is passed literally to statColour.
Inside statColour, you wrote
[[ "$1" == "0" ]]
which means that you ask, whether the string $? is equal to the string 0. This is never the case, hence the comparision is always false.
For your problem, you could try this approach (not tested, so you may have to debug it a bit):
statColour() {
# Fetch the exit code of the last program
local last_exit_code=$?
if ((last_exit_code == 0)) # Numeric comparision
then
.....
else
...
fi
# Preserve the exit code
return $last_exit_code
}
and set the prompt as
PS1='$(statColour) '"$Reset"
The single quotes ensure that statColour is evaluated dynamically, while $Reset is in double quotes since it is OK to evaluate it statically.

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.

What does the operator != mean in a shell script?

In this i have an exclamation mark in my second if statement why is this used.
#!/bin/bash
name=$1
if [ "$name" = "" ]
then echo -n "Enter a name to search for: "
read name
fi
grep -i cheryl ~uli101/uli101/phonebook
grep -i $name ~uli101/uli101/phonebook
if [ "$?" != "0" ]
then echo -n "Name '$name' not in directory "
fi
The special shell parameter $? contains the exit code from the last command run. Every command you run from the shell reports a numeric status back to the shell when it finishes running; in general, a value of 0 means the command succeeded, and a nonzero value means it failed.
The grep command searches a file for lines matching a pattern. If it finds any matching lines, it prints them out, but it also exits with status 0 if it found at least one match, and a nonzero status if it didn't find any.
The syntax [ expression ] is a command that evaluates the given expression (usually a comparison of some sort) to see if it's true or not. Really, it's just another shell command, that exits with status 0 if the expression is true and 1 if it's false; the if construct in the shell decides what to do based on the value of $?.
And the != operator means 'is not equal to', so [ $? != 0 ] is checking to see if $? is not equal to zero.
Putting all that together, the above code checks to see if the grep found a match or not.
The origin of != is the C family of programming languages, in which the exclamation point generally means "not". In bash, a ! at the start of a command will invert the exit status of the command, turning nonzero values to zero and zeroes to one. So you could also "move the exclamation point" and rewrite the above expression like this:
if ! [ $? == 0 ]
However, since if itself operates based on exit status, all of the above code is doing extra work. You can skip the middleman and just test grep directly:
if ! grep -i "$name" ~uli101/uli101/phonebook; then
echo "Name '$name' not in directory."
fi
Note that I put double quotes around $name, which prevents any spaces in the value from separating it into multiple arguments to grep.
[ "$?" != "0" ] means "$? not equal to 0". See the full list of Bash test operators here.
Note that $? will be set to 0 if grep finds a match, and 1 otherwise.

Testing to see if an env variable is set in bash

In a bash script, I'm trying to test for the existence of a variable. But no matter what I do, my "if" test returns true. Here's the code:
ignored-deps-are-not-set () {
if [ -z "${ignored_deps+x}" ]
then
return 0
fi
return 1
}
ignored_deps=1
ignored-deps-are-not-set
echo "ignored-deps function returns: $?"
if [ ignored-deps-are-not-set ]
then
echo "variable is unset"
else
echo "variable exists"
fi
Here is the output as written:
ignored-deps function returns: 1
variable is unset
And the output when I comment out the line where ignored_deps is set.
ignored-deps function returns: 0
variable is unset
No matter what, it says the variable is unset. What am I missing?
This line:
if [ ignored-deps-are-not-set ]
tests whether the string 'ignored-deps-are-not-set' is empty or not. It returns true because the string is not empty. It does not execute a command (and hence the function).
If you want to test whether a variable is set, use one of the ${variable:xxxx} notations.
if [ ${ignored_deps+x} ]
then echo "ignored_deps is set ($ignored_deps)"
else echo "ignored_deps is not set"
fi
The ${ignored_deps+x} notation evaluates to x if $ignored_deps is set, even if it is set to an empty string. If you only want it set to a non-empty value, then use a colon too:
if [ ${ignored_deps:+x} ]
then echo "ignored_deps is set ($ignored_deps)"
else echo "ignored_deps is not set or is empty"
fi
If you want to execute the function (assuming the dashes work in the function name), then:
if ignored-deps-are-not-set
then echo "Function returned a zero (success) status"
else echo "Function returned a non-zero (failure) status"
fi
You're not actually executing the function:
if ignored-deps-are-not-set; then ...
Withing [] brackets, the literal string "ignored-deps-are-not-set" is seen as true.
if [ ${myvar:-notset} -eq "notset" ] then
...
Yet another way to test for the existence of a variable:
if compgen -A variable test_existence_of_var; then
echo yes
else
echo no
fi
--edit--
just realized that it's a function tha tyou're trying to call, convention is wrong.
See:
Z000DGQD#CND131D5W6 ~
$ function a-b-c() {
> return 1
> }
Z000DGQD#CND131D5W6 ~
$ a-b-c
Z000DGQD#CND131D5W6 ~
$ echo $?
1
Z000DGQD#CND131D5W6 ~
$ if a-b-c; then echo hi; else echo ho; fi
ho
Z000DGQD#CND131D5W6 ~
$ if [ a-b-c ]; then echo hi; else echo ho; fi
hi
Z000DGQD#CND131D5W6 ~
--edit end--
Fix the variable name (see my comment to your post)
then
See Parameter Expansion section in man bash.
${parameter:?word}:
Display Error if Null or Unset. If parameter is null or unset, the expansion of word (or a message to that effect if word is not present) is written to the standard error and the shell, if it is not interactive, exits. Otherwise, the value of parameter is substituted.

What is the best way to write a wrapper function that runs commands and logs their exit code

I currently use this function to wrap executing commands and logging their execution, and return code, and exiting in case of a non-zero return code.
However this is problematic as apparently, it does double interpolation, making commands with single or double quotes in them break the script.
Can you recommend a better way?
Here's the function:
do_cmd()
{
eval $*
if [[ $? -eq 0 ]]
then
echo "Successfully ran [ $1 ]"
else
echo "Error: Command [ $1 ] returned $?"
exit $?
fi
}
"$#"
From http://www.gnu.org/software/bash/manual/bashref.html#Special-Parameters:
#
Expands to the positional parameters, starting from one. When the
expansion occurs within double quotes, each parameter expands to a
separate word. That is, "$#" is equivalent to "$1" "$2" .... If the
double-quoted expansion occurs within a word, the expansion of the
first parameter is joined with the beginning part of the original
word, and the expansion of the last parameter is joined with the last
part of the original word. When there are no positional parameters,
"$#" and $# expand to nothing (i.e., they are removed).
This means spaces in the arguments are re-quoted correctly.
do_cmd()
{
"$#"
ret=$?
if [[ $ret -eq 0 ]]
then
echo "Successfully ran [ $# ]"
else
echo "Error: Command [ $# ] returned $ret"
exit $ret
fi
}
Additional to "$#" what Douglas says, i would use
return $?
And not exit. It would exit your shell instead of returning from the function. If in cases you want to exit your shell if something went wrong, you can do that in the caller:
do_cmd false i will fail executing || exit
# commands in a row. exit as soon as the first fails
do_cmd one && do_cmd && two && do_cmd three || exit
(That way, you can handle failures and then exit gracefully).

Resources