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

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

Related

Bash prompt with background color extending to end of line

It's bash-prompt-fiddling-time for me (had to happen one day...).
I'm trying to get a 2-line prompt:
1st line with location info, and background color to the end of line
2nd line with time and exit code of previous command
I'm almost there, but I can't crack the "background color to the end of line" part. Not quite.
Putting together bits of info from several sources, and most importantly from here and here, I get this result (terminal screenshot).
As you can see, something's wrong with the COLUMNS calculations:
it does not reach the end of line
it depends on the text length on the 1st line
it gets worse when reaching the bottom of the terminal; it does reach the end of line then; the wrong line...
another weird thing: the tiny [ ] which are bracketing the 2nd prompt line; and those ONLY APPEAR AFTER A COMMAND IS ENTERED
This is my bashrc code:
PROMPT_COMMAND=__prompt_command
__prompt_command()
{
local EXIT="$?"
local Red='\[\033[1;38;5;9m\]'
local Green='\[\033[1;38;5;10m\]'
local Gray='\[\033[0;38;5;248m\]'
local BgBlue='\[\033[48;5;253;1;38;5;12m\]'
local BgPurple='\[\033[48;5;253;1;38;5;93m\]'
local None='\[\e[0m\]'
PS1="${BgPurple}\u#\h:${BgBlue}\$PWD"
printf -v TC_SPC "%${COLUMNS}s" ''
COLUMNS=$(tput cols)
PS1=`echo $PS1 | sed "s/$/$TC_SPC/; s/^\\(.\\{${COLUMNS}\\}\\) */\\1/"`
PS1+="\n${Gray}\D{%F %T}"
if [ $EXIT != 0 ]; then
PS1+=" ${Red} O_o ${None}" # Add red if exit code non 0
else
PS1+="${Green} ^_^ ${None}"
fi
}
I tried more hacking but no success.
Oh, there another more sophisticated version of the sed bit, which I also tried:
PS1=`echo $PS1 | sed "s/$/$TC_SPC/; s/^\\(\\(\\o33\\[[0-9;]*[a-zA-Z]\\)*\\)\\([^\o033]\\{${COLUMNS}\\}\\) */\\1\\3/"`
Different result (terminal screenshot) but still not OK.
At this point I'm taking any help !
Here is the working solution, thanks to Eric's "erase to end of line" hint.
PROMPT_COMMAND=__prompt_command # Func to gen PS1 after CMDs
__prompt_command()
{
local EXIT="$?" # This needs to be first (retrieves last commmand exit code)
local Red='\[\033[1;38;5;9m\]'
local Green='\[\033[1;38;5;10m\]'
local Gray='\[\033[0;38;5;248m\]'
local BgBlue='\[\033[48;5;253;1;38;5;12m\]'
local BgPurple='\[\033[48;5;253;1;38;5;93m\]'
local None='\[\e[0m\]'
PS1="${BgPurple}\u#\h:${BgBlue}\$PWD"
PS1+="\033[K" # erase to end of 1st line (background color stays)
PS1+="\n${Gray}\D{%F %T}\a"
if [ $EXIT != 0 ]; then
PS1+="${Red} O_o ${None}" # Add red if exit code non 0
else
PS1+="${Green} ^_^ ${None}"
fi
PS1+="\033[K" # erase to end of 2nd line (no more background color)
}
And here is the result (terminal screenshot). One more happy prompt owner...
Instead of:
printf -v TC_SPC "%${COLUMNS}s" ''
COLUMNS=$(tput cols)
PS1=`echo $PS1 | sed "s/$/$TC_SPC/; s/^\\(.\\{${COLUMNS}\\}\\) */\\1/"`
Use:
PS1+=$'\033[K' #erase to end of line

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

Detect empty command

Consider this PS1
PS1='\n${_:+$? }$ '
Here is the result of a few commands
$ [ 2 = 2 ]
0 $ [ 2 = 3 ]
1 $
1 $
The first line shows no status as expected, and the next two lines show the
correct exit code. However on line 3 only Enter was pressed, so I would like the
status to go away, like line 1. How can I do this?
Here's a funny, very simple possibility: it uses the \# escape sequence of PS1 together with parameter expansions (and the way Bash expands its prompt).
The escape sequence \# expands to the command number of the command to be executed. This is incremented each time a command has actually been executed. Try it:
$ PS1='\# $ '
2 $ echo hello
hello
3 $ # this is a comment
3 $
3 $ echo hello
hello
4 $
Now, each time a prompt is to be displayed, Bash first expands the escape sequences found in PS1, then (provided the shell option promptvars is set, which is the default), this string is expanded via parameter expansion, command substitution, arithmetic expansion, and quote removal.
The trick is then to have an array that will have the k-th field set (to the empty string) whenever the (k-1)-th command is executed. Then, using appropriate parameter expansions, we'll be able to detect when these fields are set and to display the return code of the previous command if the field isn't set. If you want to call this array __cmdnbary, just do:
PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '
Look:
$ PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '
0 $ [ 2 = 3 ]
1 $
$ # it seems that it works
$ echo "it works"
it works
0 $
To qualify for the shortest answer challenge:
PS1='\n${a[\#]-$? }${a[\#]=}$ '
that's 31 characters.
Don't use this, of course, as a is a too trivial name; also, \$ might be better than $.
Seems you don't like that the initial prompt is 0 $; you can very easily modify this by initializing the array __cmdnbary appropriately: you'll put this somewhere in your configuration file:
__cmdnbary=( '' '' ) # Initialize the field 1!
PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '
Got some time to play around this weekend. Looking at my earlier answer (not-good) and other answers I think this may be probably the smallest answer.
Place these lines at the end of your ~/.bash_profile:
PS1='$_ret$ '
trapDbg() {
local c="$BASH_COMMAND"
[[ "$c" != "pc" ]] && export _cmd="$c"
}
pc() {
local r=$?
trap "" DEBUG
[[ -n "$_cmd" ]] && _ret="$r " || _ret=""
export _ret
export _cmd=
trap 'trapDbg' DEBUG
}
export PROMPT_COMMAND=pc
trap 'trapDbg' DEBUG
Then open a new terminal and note this desired behavior on BASH prompt:
$ uname
Darwin
0 $
$
$
$ date
Sun Dec 14 05:59:03 EST 2014
0 $
$
$ [ 1 = 2 ]
1 $
$
$ ls 123
ls: cannot access 123: No such file or directory
2 $
$
Explanation:
This is based on trap 'handler' DEBUG and PROMPT_COMMAND hooks.
PS1 is using a variable _ret i.e. PS1='$_ret$ '.
trap command runs only when a command is executed but PROMPT_COMMAND is run even when an empty enter is pressed.
trap command sets a variable _cmd to the actually executed command using BASH internal var BASH_COMMAND.
PROMPT_COMMAND hook sets _ret to "$? " if _cmd is non-empty otherwise sets _ret to "". Finally it resets _cmd var to empty state.
The variable HISTCMD is updated every time a new command is executed. Unfortunately, the value is masked during the execution of PROMPT_COMMAND (I suppose for reasons related to not having history messed up with things which happen in the prompt command). The workaround I came up with is kind of messy, but it seems to work in my limited testing.
# This only works if the prompt has a prefix
# which is displayed before the status code field.
# Fortunately, in this case, there is one.
# Maybe use a no-op prefix in the worst case (!)
PS1_base=$'\n'
# Functions for PROMPT_COMMAND
PS1_update_HISTCMD () {
# If HISTCONTROL contains "ignoredups" or "ignoreboth", this breaks.
# We should not change it programmatically
# (think principle of least astonishment etc)
# but we can always gripe.
case :$HISTCONTROL: in
*:ignoredups:* | *:ignoreboth:* )
echo "PS1_update_HISTCMD(): HISTCONTROL contains 'ignoredups' or 'ignoreboth'" >&2
echo "PS1_update_HISTCMD(): Warning: Please remove this setting." >&2 ;;
esac
# PS1_HISTCMD needs to contain the old value of PS1_HISTCMD2 (a copy of HISTCMD)
PS1_HISTCMD=${PS1_HISTCMD2:-$PS1_HISTCMD}
# PS1_HISTCMD2 needs to be unset for the next prompt to trigger properly
unset PS1_HISTCMD2
}
PROMPT_COMMAND=PS1_update_HISTCMD
# Finally, the actual prompt:
PS1='${PS1_base#foo${PS1_HISTCMD2:=${HISTCMD%$PS1_HISTCMD}}}${_:+${PS1_HISTCMD2:+$? }}$ '
The logic in the prompt is roughly as follows:
${PS1_base#foo...}
This displays the prefix. The stuff in #... is useful only for its side effects. We want to do some variable manipulation without having the values of the variables display, so we hide them in a string substitution. (This will display odd and possibly spectacular things if the value of PS1_base ever happens to begin with foo followed by the current command history index.)
${PS1_HISTCMD2:=...}
This assigns a value to PS1_HISTCMD2 (if it is unset, which we have made sure it is). The substitution would nominally also expand to the new value, but we have hidden it in a ${var#subst} as explained above.
${HISTCMD%$PS1_HISTCMD}
We assign either the value of HISTCMD (when a new entry in the command history is being made, i.e. we are executing a new command) or an empty string (when the command is empty) to PS1_HISTCMD2. This works by trimming off the value HISTCMD any match on PS1_HISTCMD (using the ${var%subst} suffix replacement syntax).
${_:+...}
This is from the question. It will expand to ... something if the value of $_ is set and nonempty (which it is when a command is being executed, but not e.g. if we are performing a variable assignment). The "something" should be the status code (and a space, for legibility) if PS1_HISTCMD2 is nonempty.
${PS1_HISTCMD2:+$? }
There.
'$ '
This is just the actual prompt suffix, as in the original question.
So the key parts are the variables PS1_HISTCMD which remembers the previous value of HISTCMD, and the variable PS1_HISTCMD2 which captures the value of HISTCMD so it can be accessed from within PROMPT_COMMAND, but needs to be unset in the PROMPT_COMMAND so that the ${PS1_HISTCMD2:=...} assignment will fire again the next time the prompt is displayed.
I fiddled for a bit with trying to hide the output from ${PS1_HISTCMD2:=...} but then realized that there is in fact something we want to display anyhow, so just piggyback on that. You can't have a completely empty PS1_base because the shell apparently notices, and does not even attempt to perform a substitution when there is no value; but perhaps you can come up with a dummy value (a no-op escape sequence, perhaps?) if you have nothing else you want to display. Or maybe this could be refactored to run with a suffix instead; but that is probably going to be trickier still.
In response to Anubhava's "smallest answer" challenge, here is the code without comments or error checking.
PS1_base=$'\n'
PS1_update_HISTCMD () { PS1_HISTCMD=${PS1_HISTCMD2:-$PS1_HISTCMD}; unset PS1_HISTCMD2; }
PROMPT_COMMAND=PS1_update_HISTCMD
PS1='${PS1_base#foo${PS1_HISTCMD2:=${HISTCMD%$PS1_HISTCMD}}}${_:+${PS1_HISTCMD2:+$? }}$ '
This is probably not the best way to do this, but it seems to be working
function pc {
foo=$_
fc -l > /tmp/new
if cmp -s /tmp/{new,old} || test -z "$foo"
then
PS1='\n$ '
else
PS1='\n$? $ '
fi
cp /tmp/{new,old}
}
PROMPT_COMMAND=pc
Result
$ [ 2 = 2 ]
0 $ [ 2 = 3 ]
1 $
$
I need to use great script bash-preexec.sh.
Although I don't like external dependencies, this was the only thing to help me avoid to have 1 in $? after just pressing enter without running any command.
This goes to your ~/.bashrc:
__prompt_command() {
local exit="$?"
PS1='\u#\h: \w \$ '
[ -n "$LASTCMD" -a "$exit" != "0" ] && PS1='['${red}$exit$clear"] $PS1"
}
PROMPT_COMMAND=__prompt_command
[-f ~/.bash-preexec.sh ] && . ~/.bash-preexec.sh
preexec() { LASTCMD="$1"; }
UPDATE: later I was able to find a solution without dependency on .bash-preexec.sh.

Is there an elegant way to store and evaluate return values in bash scripts?

I have a rather complex series of commands in bash that ends up returning a meaningful exit code. Various places later in the script need to branch conditionally on whether the command set succeed or not.
Currently I am storing the exit code and testing it numerically, something like this:
long_running_command | grep -q trigger_word
status=$?
if [ $status -eq 0 ]; then
: stuff
else
: more code
if [ $status -eq 0 ]; then
: stuff
else
For some reason it feels like this should be simpler. We have a simple exit code stored and now we are repeatedly typing out numerical test operations to run on it. For example I can cheat use the string output instead of the return code which is simpler to test for:
status=$(long_running_command | grep trigger_word)
if [ $status ]; then
: stuff
else
: more code
if [ $status ]; then
: stuff
else
On the surface this looks more straight forward, but I realize it's dirty.
If the other logic wasn't so complex and I was only running this once, I realize I could embed it in place of the test operator, but this is not ideal when you need to reuse the results in other locations without re-running the test:
if long_running_command | grep -q trigger_word; then
: stuff
else
The only thing I've found so far is assigning the code as part of command substitution:
status=$(long_running_command | grep -q trigger_word; echo $?)
if [ $status -eq 0 ]; then
: stuff
else
Even this is not technically a one shot assignment (although some may argue the readability is better) but the necessary numerical test syntax still seems cumbersome to me. Maybe I'm just being OCD.
Am I missing a more elegant way to assign an exit code to a variable then branch on it later?
The simple solution:
output=$(complex_command)
status=$?
if (( status == 0 )); then
: stuff with "$output"
fi
: more code
if (( status == 0 )); then
: stuff with "$output"
fi
Or more eleganter-ish
do_complex_command () {
# side effects: global variables
# store the output in $g_output and the status in $g_status
g_output=$(
command -args | commands | grep -q trigger_word
)
g_status=$?
}
complex_command_succeeded () {
test $g_status -eq 0
}
complex_command_output () {
echo "$g_output"
}
do_complex_command
if complex_command_succeeded; then
: stuff with "$(complex_command_output)"
fi
: more code
if complex_command_succeeded; then
: stuff with "$(complex_command_output)"
fi
Or
do_complex_command () {
# side effects: global variables
# store the output in $g_output and the status in $g_status
g_output=$(
command -args | commands
)
g_status=$?
}
complex_command_output () {
echo "$g_output"
}
complex_command_contains_keyword () {
complex_command_output | grep -q "$1"
}
if complex_command_contains_keyword "trigger_word"; then
: stuff with "$(complex_command_output)"
fi
If you don't need to store the specific exit status, just whether the command succeeded or failed (e.g. whether grep found a match), I's use a fake boolean variable to store the result:
if long_running_command | grep trigger_word; then
found_trigger=true
else
found_trigger=false
fi
# ...later...
if ! $found_trigger; then
# stuff to do if the trigger word WASN'T found
fi
#...
if $found_trigger; then
# stuff to do if the trigger WAS found
fi
Notes:
The shell doesn't really have boolean (true/false) variables. What's actually happening here is that "true" and "false" are stored as strings in the found_trigger variable; when if $found_trigger; then executes, it runs the value of $found_trigger as a command, and it just happens that the true command always succeeds and the false command always fails, thus causing "the right thing" to happen. In if ! $found_trigger; then, the "!" toggles the success/failure status, effectively acting as a boolean "not".
if long_running_command | grep trigger_word; then is equivalent to running the command, then using if [ $? -ne 0 ]; then to check its exit status. I find it a little cleaner, but you have to get used to thinking of if as checking the success/failure of a command, not just testing boolean conditions. If "active" if commands aren't intuitive to you, use a separate test instead.
As Charles Duffy pointed out in a comment, this trick executes data as a command, and if you don't have full control over that data... you don't have control over what your script is going to do. So never set a fake-boolean variable to anything other than the fixed strings "true" and "false", and be sure to set the variable before using it. If you have any nontrivial execution flow in the script, set all fake-boolean variables to sane default values (i.e. "true" or "false") before the execution flow gets complicated.
Failure to follow these rules can lead to security holes large enough to drive a freight train through.
Why don't you set flags for the stuff that needs to happen later?
cheeseballs=false
nachos=false
guppies=false
command
case $? in
42) cheeseballs=true ;;
17 | 31) cheeseballs=true; nachos=true; guppies=true;;
66) guppies=true; echo "Bingo!";;
esac
$cheeseballs && java -crash -burn
$nachos && python ./tex.py --mex
if $guppies; then
aquarium --light=blue --door=hidden --decor=squid
else
echo SRY
fi
As pointed out by #CharlesDuffy in the comments, storing an actual command in a variable is slightly dubious, and vaguely triggers Bash FAQ #50 warnings; the code reads (slightly & IMHO) more naturally like this, but you have to be really careful that you have total control over the variables at all times. If you have the slightest doubt, perhaps just use string values and compare against the expected value at each junction.
[ "$cheeseballs" = "true" ] && java -crash -burn
etc etc; or you could refactor to some other implementation structure for the booleans (an associative array of options would make sense, but isn't portable to POSIX sh; a PATH-like string is flexible, but perhaps too unstructured).
Based on the OP's clarification that it's only about success v. failure (as opposed to the specific exit codes):
long_running_command | grep -q trigger_word || failed=1
if ((!failed)); then
: stuff
else
: more code
if ((!failed)); then
: stuff
else
Sets the success-indicator variable only on failure (via ||, i.e, if a non-zero exit code is returned).
Relies on the fact that variables that aren't defined evaluate to false in an arithmetic conditional (( ... )).
Care must be taken that the variable ($failed, in this example) hasn't accidentally been initialized elsewhere.
(On a side note, as #nos has already mentioned in a comment, you need to be careful with commands involving a pipeline; from man bash (emphasis mine):
The return status of a pipeline is the exit status of the last command,
unless the pipefail option is enabled. If pipefail is enabled, the
pipeline's return status is the value of the last (rightmost) command
to exit with a non-zero status, or zero if all commands exit successfully.
To set pipefail (which is OFF by default), use set -o pipefail; to turn it back off, use set +o pipefail.)
If you don't care about the exact error code, you could do:
if long_running_command | grep -q trigger_word; then
success=1
: success
else
success=0
: failure
fi
if ((success)); then
: success
else
: failure
fi
Using 0 for false and 1 for true is my preferred way of storing booleans in scripts. if ((flag)) mimics C nicely.
If you do care about the exit code, then you could do:
if long_running_command | grep -q trigger_word; then
status=0
: success
else
status=$?
: failure
fi
if ((status == 0)); then
: success
else
: failure
fi
I prefer an explicit test against 0 rather than using !, which doesn't read right.
(And yes, $? does yield the correct value here.)
Hmm, the problem is a bit vague - if possible, I suggest considering refactoring/simplify, i.e.
function check_your_codes {
# ... run all 'checks' and store the results in an array
}
###
function process_results {
# do your 'stuff' based on array values
}
###
create_My_array
check_your_codes
process_results
Also, unless you really need to save the exit code then there is no need to store_and_test - just test_and_do, i.e. use a case statement as suggested above or something like:
run_some_commands_and_return_EXIT_CODE_FROM_THE_LAST_ONE
if [[ $? -eq 0 ]] ; then do_stuff else do_other_stuff ; fi
:)
Dale

Bash IF not working as expected

I am trying to have a function, called from PS1 which outputs something in a different colour, depending on what that something is.
In this case it's $?, the exit status of a program.
I am trying to get this function to output red text if the exit status is anything other than 0.
I have tried all possible variations of this, ways of representing that variable in the conditions and so forth and it just isn't working.
Instead of outputting what I expect it's just either always $LRED in one variation of this IF, or always $HII in another variation of this IF.
All relevant BASH is posted below, can you guys offer any insight?
...
# Custom Colour Alias
NM="\[\033[0;38m\]" # No background and white lines
HI="\[\033[1;36m\]" # Username colour
HII="\[\033[0;37m\]" # Name colour
SI="\[\033[1;32m\]" # Directory colour
IN="\[\033[0m\]" # Command input color
LRED="\[\033[1;31m\]"
BRW="\[\033[0;33m\]"
...
exitStatus ()
{
if [ $? -ne 0 ]
then
echo "$LRED\$?"
else
echo "\$?"
fi
#echo \$?
}
...
export PS1="\n$HII[ $LRED\u $SI\w$NM $HII]\n[ \! / \# / $(exitStatus) $HII]$LRED $ $IN"
CODE BASED ON SOLUTION
This is what I did based on the accepted answer below.
# Before Prompt
export PROMPT_COMMAND='EXSO=$?;\
if [[ $EXSO != 0 ]];\
then\
ERRMSG="$LRED$EXSO";\
else\
ERRMSG="$EXSO";\
fi;\
PS1="\n$HII[ $LRED\u $SI\W$NM $HII\! / \# / $ERRMSG $HII] $SI$ $IN";'
Problem is that your assignment to PS1 is only evaluated once, thus exitStatus is only called once. As Nirk also mentions you should use PROMPT_COMMAND. Set it to the command you want executed before every new prompt is displayed. An example:
PROMPT_COMMAND='if [ $? -ne 0 ]; then echo -n FAIL:;fi'
Will yell FAIL: before every new prompt if the previous command failed:
mogul#linuxine:~$ date
Sun Sep 29 21:13:53 CEST 2013
mogul#linuxine:~$ rm crappy_on_existent_file
rm: cannot remove ‘crappy_on_existent_file’: No such file or directory
FAIL:mogul#linuxine:~$

Categories

Resources