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

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.

Related

Finding a part of a string in another string variable in bash

I have an issue in finding a part of string variable in another string variable, I tried many methods but none worked out..
for example:
echo -e " > Required_keyword: $required_keyword"
send_func GUI WhereAmI
echo -e " > FUNCVALUE: $FUNCVALUE"
flag=`echo $FUNCVALUE|awk '{print match($0,"$required_keyword")}'`;
if [ $flag -gt 0 ];then
echo "Success";
else
echo "fail";
fi
But it always gives fail though there are certain words in variable which matches like
0_Menu/BAA_Record ($required_keyword output string)
Trying to connect to 169.254.98.226 ... OK! Executing sendFunc GUI
WhereAmI Sent Function WhereAmI [OK PageName:
"_0_Menu__47__BAA_Record" ($FUNCVALUE output string)
As we can see here the BAA_Record is common in both of the output still, it always give FAIL
The output echo is
> Required_keyword: 0_Menu/BAA_Record
> FUNCVALUE:
Trying to connect to 169.254.98.226 ... OK!
Executing sendFunc GUI WhereAmI
Sent Function WhereAmI [OK]
PageName: "_0_Menu__47__BAA_Record"
Bash can do wildcard and regex matches inside double square brackets.
if [[ foobar == *oba* ]] # wildcard
if [[ foobar =~ fo*b.r ]] # regex
In your example:
if [[ $FUNCVALUE = *$required_keyword* ]]
if [[ $FUNCVALUE =~ .*$required_keyword.* ]]
Not sure if I understand what you want, but if you need to find out if there's part of string "a" present in variable "b" you can use simply just grep.
grep -q "a" <<< "$b"
[[ "$?" -eq 0 ]] && echo "Found" || echo "Not found"
EDIT: To clarify, grep searches for string a in variable b and returns exit status (see man grep, hence the -q switch). After that you can check for exit status and do whatever you want (either with my example or with regular if statement).

Use of $? in a bash function

I am having a problem with testing $? within a function, regardless of how it is passed.
__myretval () {
if [ $1 -ne 0 ]; then
printf -- "%s" "$1"
fi
}
PS1="$(__myretval '$?') $"
The goal being to have retvals show if they not 0. The function is MUCH more detailed than this and it must be in the function, please do not suggest pulling this out of the function.
$ false
1 $ true
$
I have tried every combination I can think of, but nothing seems to work, including but not limited to combinations of the following. I've tried putting the value in quotes, and without quotes; I've tried doing the same for the 0, with quotes and without.
if [ $1 -ne 0 ]; then
if [ $1 != 0 ]; then
if [ $? -ne 0 ]; then
if [ $? != 0 ]; then
PS1="$(__myretval "$?") $"
PS1="$(__myretval "\$?") $"
Either the value always prints or it never prints.
This works for me:
__myretval () {
if (($1)); then
printf -- "%s" "$1"
fi
}
PS1='$(__myretval "$?") $'
It seems your problem was with the quotes.
When you state:
PS1="$(__myretval '$?') $"
what you're doing is (because of the double quotes): setting PS1 to the output of the function __myretval with the argument '$?', where $? is expanded now. So your PS1 never changes.
What you want instead is PS1 to contain the string:
$(__myretval "$?") $
so that this string is expanded (evaluated) at each new prompt. That's why you should use single quotes to define your PS1.
You need to alternate the use of single ' and double " quotes to control when
the contained expression and variables get evaluated.
Also, why pass $? as a parameter? Add local status=$? as the first statement in the function, and you won't need any checks:
__myretval () {
local status=$?
[[ $status -eq 0 ]] && return
echo $status
}
Also, because $status is now guaranteed to contain a valid string with no surprises, we won't need printf, as $( ... ) will drop the final newline.
How you set PS1 will require appropriate quoting:
PS1="${OTHER_VARIABLE}"'$(__myretval) $ '
Double quotes evaluate when PS1 gets set, single quotes postpone that.

Simplifying advanced Bash prompt variable (PS1) code

So I've found the following cool Bash prompt:
..with the very basic logic of:
PS1="\[\033[01;37m\]\$? \$(if [[ \$? == 0 ]]; then echo \"\[\033[01;32m\]\342\234\223\"; else echo \"\[\033[01;31m\]\342\234\227\"; fi) $(if [[ ${EUID} == 0 ]]; then echo '\[\033[01;31m\]\h'; else echo '\[\033[01;32m\]\u#\h'; fi)\[\033[01;34m\] \w \$\[\033[00m\] "
However, this is not very basic and happens to be an incredible mess. I'd like to make it more readable.
How?
Use PROMPT_COMMAND to build the value up in a sane fashion. This saves a lot of quoting and makes the text much more readable. Note that you can use \e instead of \033 to represent the escape character inside a prompt.
set_prompt () {
local last_command=$? # Must come first!
PS1=""
# Add a bright white exit status for the last command
PS1+='\[\e[01;37m\]$? '
# If it was successful, print a green check mark. Otherwise, print
# a red X.
if [[ $last_command == 0 ]]; then
PS1+='\[\e[01;32m\]\342\234\223 '
else
PS1+='\[\e[01;31m\]\342\234\227 '
fi
# If root, just print the host in red. Otherwise, print the current user
# and host in green.
# in
if [[ $EUID == 0 ]]; then
PS1+='\[\e[01;31m\]\h '
else
PS1+='\[\e[01;32m\]\u#\h '
fi
# Print the working directory and prompt marker in blue, and reset
# the text color to the default.
PS1+='\[\e[01;34m\] \w \$\[\e[00m\] '
}
PROMPT_COMMAND='set_prompt'
You can define variables for the more esoteric escape sequences, at the cost of needing some extra escapes inside the double quotes, to accommodate parameter expansion.
set_prompt () {
local last_command=$? # Must come first!
PS1=""
local blue='\[\e[01;34m\]'
local white='\[\e[01;37m\]'
local red='\[\e[01;31m\]'
local green='\[\e[01;32m\]'
local reset='\[\e[00m\]'
local fancyX='\342\234\227'
local checkmark='\342\234\223'
PS1+="$white\$? "
if [[ $last_command == 0 ]]; then
PS1+="$green$checkmark "
else
PS1+="$red$fancyX "
fi
if [[ $EUID == 0 ]]; then
PS1+="$red\\h "
else
PS1+="$green\\u#\\h "
fi
PS1+="$blue\\w \\\$$reset "
}

Bash: How to set a variable from argument, and with a default value

It is pretty clear that with shell scripting this sort of thing can be accomplished in a huge number of ways (more than most programming languages) because of all the different variable expansion methods and programs like test and [ and [[, etc.
Right now I'm just looking for
DIR=$1 or .
Meaning, my DIR variable should contain either what is specified in the first arg or the current directory.
What is the difference between this and DIR=${1-.}?
I find the hyphen syntax confusing, and seek more readable syntax.
Why can't I do this?
DIR="$1" || '.'
I'm guessing this means "if $1 is empty, the assignment still works (DIR becomes empty), so the invalid command '.' never gets executed."
I see several questions here.
“Can I write something that actually reflects this logic”
Yes. There are a few ways you can do it. Here's one:
if [[ "$1" != "" ]]; then
DIR="$1"
else
DIR=.
fi
“What is the difference between this and DIR=${1-.}?”
The syntax ${1-.} expands to . if $1 is unset, but expands like $1 if $1 is set—even if $1 is set to the empty string.
The syntax ${1:-.} expands to . if $1 is unset or is set to the empty string. It expands like $1 only if $1 is set to something other than the empty string.
“Why can't I do this? DIR="$1" || '.'”
Because this is bash, not perl or ruby or some other language. (Pardon my snideness.)
In bash, || separates entire commands (technically it separates pipelines). It doesn't separate expressions.
So DIR="$1" || '.' means “execute DIR="$1", and if that exits with a non-zero exit code, execute '.'”.
How about this:
DIR=.
if [ $# -gt 0 ]; then
DIR=$1
fi
$# is the number of arguments given to the script, and -gt means "greater than", so you basically set DIR to the default value, and if the user has specified an argument, then you set DIR to that instead.
I use a simple helper function to make such assignments look cleaner. The function below accepts any number of arguments, but returns the first one that's not the empty string.
default_value() {
# Return the first non-empty argument
while [[ "$1" == "" ]] && [[ "$#" -gt "0" ]]; do
shift
done
echo $1
}
x=$(default_value "$1" 0)

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

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"

Resources