Bash script, check numeric variable in in range - bash

This is my script:
#!/bin/bash
JOB_NUM=4
function checkJobNumber() {
if (( $JOB_NUM < 1 || $JOB_NUM > 16 )); then
echo "pass"
fi
}
...
checkJobNumber
...
When I try to launch the script I get the message:
./script.sh line 49: ((: < 1 || > 16 : syntax error: operand expected (error token is "< 1 || > 16 ")
(Please notice the spaces in the error message)
I really don't understand what the problem is. If I do the evaluation manually at the command line, it works.
Also, I tried different evaluations like if [[ "$JOB_NUM" -lt 1 -o "$JOB_NUM" gt 16 ]];... still no success.

UPDATE: As suggested I made few more attempts in the rest of the code outside the function call, and I found the problem.
My variables declaration was actually indented this way:
JOB_NUM= 4
THREAD_NUM= 8
.....
VERY_LONG_VAR_NAME= 3
and apparently this hoses the evaulation. BAH! It always worked for me before, so why doesn’t it now?
If I delete the white spaces, the evaluation works:
JOB_NUM=4
THREAD_NUM=8
....
VERY_LONG_VAR_NAME=3
OK I officially hate bash . . . sigh :(

this will work fine
#!/bin/bash
JOB_NUM=7
function checkJobNumber() {
if [ $JOB_NUM -lt 0 ] || [ $JOB_NUM -gt 16 ] ; then
echo "true"
else
echo "false"
fi
}
checkJobNumber
And if you want to check variable during tests you can write in your bash :
set -u
this will generate a message like "JOB_NUM: unbound variable" if variable is not well set

Related

Bash error code doesn't show in my shell prompt [duplicate]

I've been trying to customize my Bash prompt so that it will look like
[feralin#localhost ~]$ _
with colors. I managed to get constant colors (the same colors every time I see the prompt), but I want the username ('feralin') to appear red, instead of green, if the last command had a nonzero exit status. I came up with:
\e[1;33m[$(if [[ $? == 0 ]]; then echo "\e[0;31m"; else echo "\e[0;32m"; fi)\u\e[m#\e[1;34m\h \e[0;35m\W\e[1;33m]$ \e[m
However, from my observations, the $(if ...; fi) seems to be evaluated once, when the .bashrc is run, and the result is substituted forever after. This makes the name always green, even if the last exit code is nonzero (as in, echo $?). Is this what is happening? Or is it simply something else wrong with my prompt? Long question short, how do I get my prompt to use the last exit code?
As you are starting to border on a complex PS1, you might consider using PROMPT_COMMAND. With this, you set it to a function, and it will be run after each command to generate the prompt.
You could try the following in your ~/.bashrc file:
PROMPT_COMMAND=__prompt_command # Function to generate PS1 after CMDs
__prompt_command() {
local EXIT="$?" # This needs to be first
PS1=""
local RCol='\[\e[0m\]'
local Red='\[\e[0;31m\]'
local Gre='\[\e[0;32m\]'
local BYel='\[\e[1;33m\]'
local BBlu='\[\e[1;34m\]'
local Pur='\[\e[0;35m\]'
if [ $EXIT != 0 ]; then
PS1+="${Red}\u${RCol}" # Add red if exit code non 0
else
PS1+="${Gre}\u${RCol}"
fi
PS1+="${RCol}#${BBlu}\h ${Pur}\W${BYel}$ ${RCol}"
}
This should do what it sounds like you want. Take a look a my bashrc's sub file if you want to see all the things I do with my __prompt_command function.
If you don't want to use the prompt command there are two things you need to take into account:
getting the value of $? before anything else. Otherwise it'll be overridden.
escaping all the $'s in the PS1 (so it's not evaluated when you assign it)
Working example using a variable
PS1="\$(VALU="\$?" ; echo \$VALU ; date ; if [ \$VALU == 0 ]; then echo zero; else echo nonzero; fi) "
Working example without a variable
Here the if needs to be the first thing, before any command that would override the $?.
PS1="\$(if [ \$? == 0 ]; then echo zero; else echo nonzero; fi) "
Notice how the \$() is escaped so it's not executed right away, but each time PS1 is used. Also all the uses of \$?.
Compact solution:
PS1='... $(code=${?##0};echo ${code:+[error: ${code}]})'
This approach does not require PROMPT_COMMAND (apparently this can be slower sometimes) and prints [error: <code>] if the exit code is non-zero, and nothing if it's zero:
... > false
... [error: 1]> true
... >
Change the [error: ${code}] part depending on your liking, with ${code} being the non-zero code to print.
Note the use of ' to ensure the inline $() shell gets executed when PS1 is evaluated later, not when the shell is started.
As bonus, you can make it colorful in red by adding \e[01;31m in front and \e[00m after to reset:
PS1='... \e[01;31m$(code=${?##0};echo ${code:+[error: ${code}]})\e[00m'
--
How it works:
it uses bash parameter substitution
first, the ${?##0} will read the exit code $? of the previous command
the ## will remove any 0 pattern from the beginning, effectively making a 0 result an empty var (thanks #blaskovicz for the trick)
we assign this to a temporary code variable as we need to do another substitution, and they can't be nested
the ${code:+REPLACEMENT} will print the REPLACEMENT part only if the variable code is set (non-empty)
this way we can add some text and brackets around it, and reference the variable again inline: [error: ${code}]
I wanted to keep default Debian colors, print the exact code, and only print it on failure:
# Show exit status on failure.
PROMPT_COMMAND=__prompt_command
__prompt_command() {
local curr_exit="$?"
local BRed='\[\e[0;91m\]'
local RCol='\[\e[0m\]'
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u#\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
if [ "$curr_exit" != 0 ]; then
PS1="[${BRed}$curr_exit${RCol}]$PS1"
fi
}
The following provides a leading green check mark when the exit code is zero and a red cross in all other cases. The remainder is a standard colorized prompt. The printf statements can be modified to present the two states that were originally requested.
PS1='$(if [ $? -eq 0 ]; then printf "\033[01;32m""\xE2\x9C\x93"; else printf "\033[01;31m""\xE2\x9C\x95"; fi) \[\e[00;32m\]\u#\h\[\e[00;30m\]:\[\e[01;33m\]\w\[\e[01;37m\]\$ '
Why didn't I think about that myself? I found this very interesting and added this feature to my 'info-bar' project. Eyes will turn red if the last command failed.
#!/bin/bash
eyes=(O o ∘ ◦ ⍤ ⍥) en=${#eyes[#]} mouth='_'
face () { # gen random face
[[ $error -gt 0 ]] && ecolor=$RED || ecolor=$YLW
if [[ $1 ]]; then printf "${eyes[$[RANDOM%en]]}$mouth${eyes[$[RANDOM%en]]}"
else printf "$ecolor${eyes[$[RANDOM%en]]}$YLW$mouth$ecolor${eyes[$[RANDOM%en]]}$DEF"
fi
}
info () { error=$?
[[ -d .git ]] && { # If in git project folder add git status to info bar output
git_clr=('GIT' $(git -c color.ui=always status -sb)) # Colored output 4 info
git_tst=('GIT' $(git status -sb)) # Simple output 4 test
}
printf -v line "%${COLUMNS}s" # Set border length
date=$(printf "%(%a %d %b %T)T") # Date & time 4 test
test=" O_o $PWD ${git_tst[*]} $date o_O " # Test string
step=$[$COLUMNS-${#test}]; [[ $step -lt 0 ]] && step=0 # Count spaces
line="$GRN${line// /-}$DEF\n" # Create lines
home="$BLD$BLU$PWD$DEF" # Home dir info
date="$DIM$date$DEF" # Colored date & time
#------+-----+-------+--------+-------------+-----+-------+--------+
# Line | O_o |homedir| Spaces | Git status | Date| o_O | Line |
#------+-----+-------+--------+-------------+-----+-------+--------+
printf "$line $(face) $home %${step}s ${git_clr[*]} $date $(face) \n$line" # Final info string
}
PS1='${debian_chroot:+($debian_chroot)}\n$(info)\n$ '
case "$TERM" in xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)} $(face 1) \w\a\]$PS1";;
esac
Improved demure answer:
I think this is important because the exit status is not always 0 or 1.
if [ $EXIT != 0 ]; then
PS1+="${Red}${EXIT}:\u${RCol}" # Add red if exit code != 0
else
PS1+="${Gre}${EXIT}:\u${RCol}" # Also displays exit status
fi
To preserve the original prompt format (not just colors),
you could append following to the end of file ~/.bashrc:
PS1_ORIG=$PS1 # original primary prompt value
PROMPT_COMMAND=__update_prompt # Function to be re-evaluated after each command is executed
__update_prompt() {
local PREVIOUS_EXIT_CODE="$?"
if [ $PREVIOUS_EXIT_CODE != 0 ]; then
local RedCol='\[\e[0;31m\]'
local ResetCol='\[\e[0m\]'
local replacement="${RedCol}\u${ResetCol}"
# Replace username color
PS1=${PS1_ORIG//]\\u/]$replacement}
## Alternative: keep same colors, append exit code
#PS1="$PS1_ORIG[${RedCol}error=$PREVIOUS_EXIT_CODE${ResetCol}]$ "
else
PS1=$PS1_ORIG
fi
}
See also the comment about the alternative approach that preserves username color and just appends an error code in red to the end of the original prompt format.
You can achieve a similar result to include a colored (non-zero) exit code in a prompt, without using subshells in the prompt nor prompt_command.
You color the exit code portion of the prompt, while having it only appear when non-zero.
Core 2$ section of the prompt: \\[\\033[0;31;4m\\]\${?#0}\\[\\033[0;33m\\]\$ \\[\\033[0m\\]
Key elements:
return code, if not 0: \${?#0} (specificly "removes prefix of 0")
change color without adding to calculated prompt-width: \\[\\033[0;31m\\]
\\[ - begin block
\\033 - treat as 0-width, in readline calculations for cmdline editing
[0;31;4m - escape code, change color, red fg, underline
\\] - end block
Components:
\\[\\033[0;31;4m\\] - set color 0;31m fg red, underline
\${?#0} - display non-zero status (by removing 0 prefix)
\\[\\033[0;33m\\] - set color 0;33m fg yellow
\$ - $ or # on EUID
\\[\\033[0m\\] - reset color
The full PS1 I use (on one host):
declare -x PS1="\\[\\033[0;35m\\]\\h\\[\\033[1;37m\\] \\[\\033[0;37m\\]\\w \\[\\033[0;33m\\]\\[\\033[0;31;4m\\]\${?#0}\\[\\033[0;33m\\]\$ \\[\\033[0m\\]"
Note: this addresses a natural extension to this question, in a more enduring way then a comment.
Bash
function my_prompt {
local retval=$?
local field1='\u#\h'
local field2='\w'
local field3='$([ $SHLVL -gt 1 ] && echo \ shlvl:$SHLVL)$([ \j -gt 0 ] && echo \ jobs:\j)'"$([ ${retval} -ne 0 ] && echo \ exit:$retval)"
local field4='\$'
PS1=$'\n'"\e[0;35m${field1}\e[m \e[0;34m${field2}\e[m\e[0;31m${field3}\e[m"$'\n'"\[\e[0;36m\]${field4}\[\e[m\] "
}
PROMPT_COMMAND="my_prompt; ${PROMPT_COMMAND}"
Zsh
PROMPT=$'\n''%F{magenta}%n#%m%f %F{blue}%~%f%F{red}%(2L. shlvl:%L.)%(1j. jobs:%j.)%(?.. exit:%?)%f'$'\n''%F{cyan}%(!.#.$)%f '
Images of prompt

How to empty the value of $? variable in .bashrc? [duplicate]

This question already has answers here:
Detect empty command
(5 answers)
Closed 7 years ago.
I've been trying to customize my bash prompt so that it'll look like
┌─[error_code_if_not_zero]─[time_short]─[username]─[current_folder]─[git_branch]
└─▪
And here is my .bashrc:
# command completion
source /home/falcon/.bin/git-prompt.sh
GIT_PS1_SHOWDIRTYSTATE=1
GIT_PS1_SHOWSTASHSTATE=1
GIT_PS1_SHOWUNTRACKEDFILES=1
GIT_PS1_SHOWUPSTREAM="auto"
# function to generate the prompt
function __prompt_command() {
__exit_code="$?"
__error_int="";
if [ $__exit_code -ne 0 ]; then
__error_int="[\[\e[0;31m\]$__exit_code\[\e[0;37m\]]─"
fi
PS1="\[\e[0;37m\]┌─$__error_int[\A]─[\[\e[0;35m\]\u\[\e[0;37m\]]─[\[\e[0;33m\]\w\[\e[0;37m\]]\$(__git_ps1 '─[\[\e[0;31m\]%s\[\e[0;37m\]]')\n\[\e[0;37m\]└─▪ \[\e[0;m\]"
}
export PROMPT_COMMAND=__prompt_command
This configuration works fine, it is showing the error code when it's non-zero. But the trouble comes whem i'm just pressing enter in terminal (calling empty commands) - the returning value remains the same as the returning value of the last non-empty command. For example, this happened when i'm just pressing enter in terminal:
┌─[127]─[02:51]─[falcon]─[~]
└─▪
┌─[127]─[02:51]─[falcon]─[~]
└─▪
┌─[127]─[02:51]─[falcon]─[~]
└─▪
┌─[127]─[02:51]─[falcon]─[~]
└─▪
As you can see, the error code 127 remains even after an empty command.
But i'm expecting something like this:
┌─[127]─[02:51]─[falcon]─[~]
└─▪
┌─[02:51]─[falcon]─[~]
└─▪
┌─[02:51]─[falcon]─[~]
└─▪
┌─[02:51]─[falcon]─[~]
└─▪
So, my question is, how to empty the value of $? inside the function __prompt_command?
Got it. First, credit where it's due--anubhava in the mentioned "Detect Empty Command" question is the author of much of this code.
Still, it works the way you want (as far as I can tell).
# command completion
source /home/falcon/.bin/git-prompt.sh
GIT_PS1_SHOWDIRTYSTATE=1
GIT_PS1_SHOWSTASHSTATE=1
GIT_PS1_SHOWUNTRACKEDFILES=1
GIT_PS1_SHOWUPSTREAM="auto"
# function to generate the prompt
PS1="\[\e[0;37m\]┌─\$([[ -n \$_ret ]] && echo \"[\[\e[0;31m\]\$_ret\[\e[0;37m\]]-\")[\A]─[\[\e[0;32m\]\u\[\e[0;37m\]]─[\[\e[0;33m\]\w\[\e[0;37m\]]\$(__git_ps1 '─[\[\e[0;31m\]%s\[\e[0;37m\]]')\n\[\e[0;37m\]└─▪ \[\e[0;m\]"
trapDbg() {
local c="$BASH_COMMAND"
[[ "$c" != "pc" ]] && export _cmd="$c"
}
pc() {
local r=$?
if [[ $r == 0 ]]; then
r=''
fi
trap "" DEBUG
[[ -n "$_cmd" ]] && _ret="$r" || _ret=""
export _ret
export _cmd=
trap 'trapDbg' DEBUG
}
export PROMPT_COMMAND=pc
trap 'trapDbg' DEBUG
I combined your code and his, and modified the PS1. It now includes logic to only display the square brackets when $_ret is set. Also, anubhava's code always displayed a return code, including 0. I added the conditional bit to unset when return code was 0.
Anyhow, there you have it.
Note: I don't have whatever git-prompt.sh contains, so I tested without that bit. Hopefully that doesn't drastically change anything.
What if you "invoke" /bin/true as part of the function after you've used the $? value, that should always set it to 0

How to get the real line number of a failing Bash command?

In the process of coming up with a way to catch errors in my Bash scripts, I've been experimenting with "set -e", "set -E", and the "trap" command. In the process, I've discovered some strange behavior in how $LINENO is evaluated in the context of functions. First, here's a stripped down version of how I'm trying to log errors:
#!/bin/bash
set -E
trap 'echo Failed on line: $LINENO at command: $BASH_COMMAND && exit $?' ERR
Now, the behavior is different based on where the failure occurs. For example, if I follow the above with:
echo "Should fail at: $((LINENO + 1))"
false
I get the following output:
Should fail at: 6
Failed on line: 6 at command: false
Everything is as expected. Line 6 is the line containing the single command "false". But if I wrap up my failing command in a function and call it like this:
function failure {
echo "Should fail at $((LINENO + 1))"
false
}
failure
Then I get the following output:
Should fail at 7
Failed on line: 5 at command: false
As you can see, $BASH_COMMAND contains the correct failing command: "false", but $LINENO is reporting the first line of the "failure" function definition as the current command. That makes no sense to me. Is there a way to get the line number of the line referenced in $BASH_COMMAND?
It's possible this behavior is specific to older versions of Bash. I'm stuck on 3.2.51 for the time being. If the behavior has changed in later releases, it would still be nice to know if there's a workaround to get the value I want on 3.2.51.
EDIT: I'm afraid some people are confused because I broke up my example into chunks. Let me try to clarify what I have, what I'm getting, and what I want.
This is my script:
#!/bin/bash
set -E
function handle_error {
local retval=$?
local line=$1
echo "Failed at $line: $BASH_COMMAND"
exit $retval
}
trap 'handle_error $LINENO' ERR
function fail {
echo "I expect the next line to be the failing line: $((LINENO + 1))"
command_that_fails
}
fail
Now, what I expect is the following output:
I expect the next line to be the failing line: 14
Failed at 14: command_that_fails
Now, what I get is the following output:
I expect the next line to be the failing line: 14
Failed at 12: command_that_fails
BUT line 12 is not command_that_fails. Line 12 is function fail {, which is somewhat less helpful. I have also examined the ${BASH_LINENO[#]} array, and it does not have an entry for line 14.
For bash releases prior to 4.1, a special level of awful, hacky, performance-killing hell is needed to work around an issue wherein, on errors, the system jumps back to the function definition point before invoking an error handler.
#!/bin/bash
set -E
set -o functrace
function handle_error {
local retval=$?
local line=${last_lineno:-$1}
echo "Failed at $line: $BASH_COMMAND"
echo "Trace: " "$#"
exit $retval
}
if (( ${BASH_VERSION%%.*} <= 3 )) || [[ ${BASH_VERSION%.*} = 4.0 ]]; then
trap '[[ $FUNCNAME = handle_error ]] || { last_lineno=$real_lineno; real_lineno=$LINENO; }' DEBUG
fi
trap 'handle_error $LINENO ${BASH_LINENO[#]}' ERR
fail() {
echo "I expect the next line to be the failing line: $((LINENO + 1))"
command_that_fails
}
fail
BASH_LINENO is an array. You can refer to different values in it: ${BASH_LINENO[1]}, ${BASH_LINENO[2]}, etc. to back up the stack. (Positions in this array line up with those in the BASH_SOURCE array, if you want to get fancy and actually print a stack trace).
Even better, though, you can just inject the correct line number in your trap:
failure() {
local lineno=$1
echo "Failed at $lineno"
}
trap 'failure ${LINENO}' ERR
You might also find my prior answer at https://stackoverflow.com/a/185900/14122 (with a more complete error-handling example) interesting.
That behaviour is very reasonable.
The whole picture of the call stack provides comprehensive information whenever an error occurs. Your example had demonstrated a good error message; you could see where the an error actually occurred and which line triggered the function, etc.
If the interpreter/compiler can't precisely indicate where the error actually occurs, you could be more easily confused.

Return value of function in IF statement shows strange behaviour

Can anyone explain what is going on here?
Why does an "IF" statement think the return is '1' (not Null) when i say 'Return 0' and the other way round.
I found that out while coding another script, so i developed this small script to test it:
#!/bin/bash
function testreturnzero()
{
echo $1
return 0
}
function testreturnone()
{
echo $1
return 1
}
if (testreturnzero 1) || (testreturnzero 2)
then
echo "zero returned '1'"
fi
if (testreturnone 1) || (testreturnone 2)
then
echo "one returned '1'"
fi
The IF which refers to the 'return 0' thinks its true (and doesn't process the second function), the IF which refers to 'return 1' thinks its false. Shouldn't it be the exact opposite?
1
zero returned '1'
1
2
I cant put the return value in a variable as I will have several of those checks.
In bash, a function returning 0 means success and returning a non-zero value means failure. Hence your testreturnzero succeeds and your testreturnone fails.
Does that help understanding why your ifs behave that way? (it should!).
The return code of the last executed command/function is stored in the special variable $?.
So:
testreturnzero 0
ret_testreturnzero=$?
testreturnone 1
ret_testreturnone=$?
echo "$ret_testreturnzero"
echo "$ret_testreturnone"
will output (the two last lines):
0
1
Now you may think of storing them in a variable (as here) and do your logic processing later. But there's a catch :). Because you didn't store true and false in variables, you stored 0 and 1 (bash can't store booleans in a variable). So to check success or failure later:
if ((ret_testreturnzero==0)); then
echo "testreturnzero succeeded"
fi
or
if ((ret_testreturnzero!=0)); then
echo "testreturnzero failed"
fi
In bash, the return code of a function is the same as a external program when you test the result.
So for test, a valid return code is 0 and an invalid is any other number
so, by doing
if ( testreturnone 1 ); then #it is ok
echo "error"; #it's supposed to happen, not an error
fi
You can explicitly test the value to the one you want to clear it up:
if [[ "$(testreturnzero 1)" = "1"); then #it is ok if you decide that 1 is the good value
echo "ok"; #But absolutly not the bash philosophy
fi

bash exit status always 0

I'm experiencing this weird issue where my exit status always return 0 even when it didn't execute successfully.
I want to output the exit status on my prompt with the following code:
function status() {
echo $?
}
export PS1="\$(status)>"
When I run this, I get the following output
0❯ pwd
/Users/tringuyen
0❯ ad
bash: ad: command not found
0❯ echo $?
127
clearly the second last command ad didn't return a 0 status code. However that's what I got from the prompt.
Does anyone know what might be going on here?
EDIT 6/20 11:57AM: The issue seems to be that $? is always 0 no matter what, except there was an error within the .bashrc file itself, which will cause it to return a value different from 0.
Does the following work for you with your bash version?
export PS1="\$?>"
I use the following in my $PS1:
PS1="\`if [ \$? = 0 ]; then echo \[\e[33m\]^_^\[\e[0m\]; else echo \[\e[31m\]\$? O_O\[\e[0m\]; fi\`"
Src: https://github.com/sanmiguel/dotfiles/blob/master/bash/bash_functions.symlink#L63
I also had similar problem but my function looked different. The problem was, I was missing semicolon ";" after VAR=$?
OLD:
function status() {
VAR=$?
echo $VAR
}
Always returned Zero no matter what.
NEW:
function status() {
VAR=$?;
echo VAR;
}
Now returned proper return value.
export PS1="\$(status)>"

Resources