Zsh prompt with directory and virtual environment - terminal

I am having trouble finding a succinct answer to this. I am using Oh My Zsh, and right now using the default theme, robbyrussell.
I would like my prompt to have 2 components:
The current directory, plus one level up
If I am using a virtual environment (like anaconda), have the name of the active environment in parentheses.

Below is the way to do it, you can customize it as per your needs
# Helper method to add background and foreground colors
prompt_segment () {
local bg fg
[[ -n $1 ]] && bg="%K{$1}" || bg="%k"
[[ -n $2 ]] && fg="%F{$2}" || fg="%f"
if [[ $CURRENT_BG != 'NONE' && $1 != $CURRENT_BG ]]
then
echo -n " %{$bg%F{$CURRENT_BG}%}$SEGMENT_SEPARATOR%{$fg%} "
else
echo -n "%{$bg%}%{$fg%} "
fi
CURRENT_BG=$1
[[ -n $3 ]] && echo -n $3
}
prompt_virtualenv () {
# Check if we are in a virtual environment
# if we are then VIRTUAL_ENV variable will be set
local virtualenv_path="$VIRTUAL_ENV"
if [[ -n $virtualenv_path && -n $VIRTUAL_ENV_DISABLE_PROMPT ]]
then
# We are in virtual env so show just the project name
prompt_segment blue black "(`basename $virtualenv_path`)"
fi
}
prompt_directory() {
# Show the current directory
prompt_segment red blue $PWD
}
build_my_zsh_prompt() {
# Call all the prompt functions to build the actual prompt
prompt_virtualenv
prompt_directory
prompt_segment black white ""
}
# Assign the PROMPT variable with the function, so bash call it everytime
# Single quotes are important here, else you will get a fixed PROMPT
# Without single quotes, the function will be called once and evaluated value
# will be assigned
PROMPT='$(build_my_zsh_prompt)'
PROMPT variable is used by zsh shell to determine what needs to be displayed as the prompt. When we set PROMPT=$(build_my_zsh_prompt), we are asking shell to call our function build_my_zsh_prompt.
This function in turn (ideally) should call different function which create individual parts of prompt. Now let's look at prompt_directory
prompt_segment black red $PWD"
The prompt_segment is a helper function to echo some text with background and the foreground color
First parameter black is the background and second parameter red is the foreground color in this case. Next we show what text needs to be given for this prompt.
All of this needs to be added to your ~/.zshrc file at the very end

Related

How can I invoke another program's bash completion handler for a single subcommand of my program?

Context
I have an arduino-cli wrapper script named ino that reads target/build configuration from:
JSON files located in the sketch directory
Command-line flags/arguments
It then constructs and exec's the corresponding arduino-cli command-line.
Problem
As a convenience wrapper script, ino isn't intended to support every feature of arduino-cli. So for those tasks that ino doesn't automate, the user can instead invoke arduino-cli indirectly using the cli subcommand of ino.
For example, if the user types the following commands:
% ino cli update
% ino cli core list --all
The ino script will take everything following cli and simply append them to the arduino-cli executable. So they would be equivalent to the following commands:
% arduino-cli update
% arduino-cli core list --all
Since arduino-cli has nice bash completion for all of its subcommands and flags, I would like to hijack the same completion functionality for my ino cli subcommand.
What I've tried
The accepted answers here:
How do I autocomplete nested, multi-level subcommands?
Multi Level Bash Completion
These helped me understand how to identify the current subcommand and discriminate the completion results based upon it.
However, I couldn't figure out how to then invoke the arduino-cli completion handler using the remaining args.
Completion handler derived from accepted answer here:
How do I get bash completion for command aliases?
See my ino completion handler based on that answer below (Reference 1).
This question/answer isn't quite the same, because they can basically just install a completion handler on their alias. I'm needing to "install" one on an argument to a command/alias.
This almost seems to work. Try it with xtrace option enabled (set -x), and you can see the arduino-cli command-line is appearing in the args ... but following ino at position $0.
E.g., given ino cli core list --all to the wrapper handler, the arduino-cli handler receives ino arduino-cli core list --all. Not sure how to get rid of $0!
Reference
ino completion wrapper derived from alias-based wrappers
joinstr() {
local d=${1-} f=${2-}
shift 2 && printf %s "$f" "${#/#/$d}"
}
complete-subcmd() {
[[ ${#} -gt 2 ]] || {
printf "usage:\n\tcomplete-subcmd src-command... -- comp-func dst-command...\n"
return 1
}
# parse the command-line by splitting it into two command-lines
# of variable length, src-command and dst-command:
# 1. src-command is the trigger that invokes the real completion
# handler, comp-func.
# 2. dst-command is the leading args of the command-line passed
# to the real completion handler, comp-func, to produce the
# resulting completion choices.
unset -v dstparse
local -a srccmd dstcmd
local func
while [[ ${#} -gt 0 ]]; do
case "${1}" in
--)
# when we reach the delimiter, also shift in comp-func as
# the next argument (the real completion handler).
dstparse=1
shift
func=${1:-}
;;
*)
# if we aren't processing the delimiter, then all other
# args are appended to either src-command or dst-command.
if [[ -z ${dstparse} ]]; then
srccmd+=( "${1}" )
else
dstcmd+=( "${1}" )
fi
;;
esac
shift
done
# if the completer is dynamic and not yet loaded, try to load it
# automatically using the given command
if [[ $( type -t "${func}" ) != function ]]; then
type -p _completion_loader &> /dev/null &&
_completion_loader "${dstcmd[#]}"
fi
local wrap=$( joinstr _ "${srccmd[#]}" | tr -d -c '[A-Za-z_]' )
# replace our args with dst-command followed by whatever remains
# from the invoking command-line.
eval "
function _${wrap} {
(( COMP_CWORD+=$(( ${#dstcmd[#]} )) ))
COMP_WORDS=( "${dstcmd[#]}" \${COMP_WORDS[#]:1} )
"${func}"
return 0
}
"
# install this wrapper handler on the first word in src-command
complete -F "_${wrap}" "${srccmd[0]}"
}
complete-subcmd ino cli -- __start_arduino-cli arduino-cli
completion.bash from arduino-cli
# bash completion V2 for arduino-cli -*- shell-script -*-
__arduino-cli_debug()
{
if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
fi
}
# Macs have bash3 for which the bash-completion package doesn't include
# _init_completion. This is a minimal version of that function.
__arduino-cli_init_completion()
{
COMPREPLY=()
_get_comp_words_by_ref "$#" cur prev words cword
}
# This function calls the arduino-cli program to obtain the completion
# results and the directive. It fills the 'out' and 'directive' vars.
__arduino-cli_get_completion_results() {
local requestComp lastParam lastChar args
# Prepare the command to request completions for the program.
# Calling ${words[0]} instead of directly arduino-cli allows to handle aliases
args=("${words[#]:1}")
requestComp="${words[0]} __completeNoDesc ${args[*]}"
lastParam=${words[$((${#words[#]}-1))]}
lastChar=${lastParam:$((${#lastParam}-1)):1}
__arduino-cli_debug "lastParam ${lastParam}, lastChar ${lastChar}"
if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then
# If the last parameter is complete (there is a space following it)
# We add an extra empty parameter so we can indicate this to the go method.
__arduino-cli_debug "Adding extra empty parameter"
requestComp="${requestComp} ''"
fi
# When completing a flag with an = (e.g., arduino-cli -n=<TAB>)
# bash focuses on the part after the =, so we need to remove
# the flag part from $cur
if [[ "${cur}" == -*=* ]]; then
cur="${cur#*=}"
fi
__arduino-cli_debug "Calling ${requestComp}"
# Use eval to handle any environment variables and such
out=$(eval "${requestComp}" 2>/dev/null)
# Extract the directive integer at the very end of the output following a colon (:)
directive=${out##*:}
# Remove the directive
out=${out%:*}
if [ "${directive}" = "${out}" ]; then
# There is not directive specified
directive=0
fi
__arduino-cli_debug "The completion directive is: ${directive}"
__arduino-cli_debug "The completions are: ${out[*]}"
}
__arduino-cli_process_completion_results() {
local shellCompDirectiveError=1
local shellCompDirectiveNoSpace=2
local shellCompDirectiveNoFileComp=4
local shellCompDirectiveFilterFileExt=8
local shellCompDirectiveFilterDirs=16
if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
# Error code. No completion.
__arduino-cli_debug "Received error from custom completion go code"
return
else
if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
if [[ $(type -t compopt) = "builtin" ]]; then
__arduino-cli_debug "Activating no space"
compopt -o nospace
else
__arduino-cli_debug "No space directive not supported in this version of bash"
fi
fi
if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
if [[ $(type -t compopt) = "builtin" ]]; then
__arduino-cli_debug "Activating no file completion"
compopt +o default
else
__arduino-cli_debug "No file completion directive not supported in this version of bash"
fi
fi
fi
if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
# File extension filtering
local fullFilter filter filteringCmd
# Do not use quotes around the $out variable or else newline
# characters will be kept.
for filter in ${out[*]}; do
fullFilter+="$filter|"
done
filteringCmd="_filedir $fullFilter"
__arduino-cli_debug "File filtering command: $filteringCmd"
$filteringCmd
elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
# File completion for directories only
# Use printf to strip any trailing newline
local subdir
subdir=$(printf "%s" "${out[0]}")
if [ -n "$subdir" ]; then
__arduino-cli_debug "Listing directories in $subdir"
pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
else
__arduino-cli_debug "Listing directories in ."
_filedir -d
fi
else
__arduino-cli_handle_standard_completion_case
fi
__arduino-cli_handle_special_char "$cur" :
__arduino-cli_handle_special_char "$cur" =
}
__arduino-cli_handle_standard_completion_case() {
local tab comp
tab=$(printf '\t')
local longest=0
# Look for the longest completion so that we can format things nicely
while IFS='' read -r comp; do
# Strip any description before checking the length
comp=${comp%%$tab*}
# Only consider the completions that match
comp=$(compgen -W "$comp" -- "$cur")
if ((${#comp}>longest)); then
longest=${#comp}
fi
done < <(printf "%s\n" "${out[#]}")
local completions=()
while IFS='' read -r comp; do
if [ -z "$comp" ]; then
continue
fi
__arduino-cli_debug "Original comp: $comp"
comp="$(__arduino-cli_format_comp_descriptions "$comp" "$longest")"
__arduino-cli_debug "Final comp: $comp"
completions+=("$comp")
done < <(printf "%s\n" "${out[#]}")
while IFS='' read -r comp; do
COMPREPLY+=("$comp")
done < <(compgen -W "${completions[*]}" -- "$cur")
# If there is a single completion left, remove the description text
if [ ${#COMPREPLY[*]} -eq 1 ]; then
__arduino-cli_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
comp="${COMPREPLY[0]%% *}"
__arduino-cli_debug "Removed description from single completion, which is now: ${comp}"
COMPREPLY=()
COMPREPLY+=("$comp")
fi
}
__arduino-cli_handle_special_char()
{
local comp="$1"
local char=$2
if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then
local word=${comp%"${comp##*${char}}"}
local idx=${#COMPREPLY[*]}
while [[ $((--idx)) -ge 0 ]]; do
COMPREPLY[$idx]=${COMPREPLY[$idx]#"$word"}
done
fi
}
__arduino-cli_format_comp_descriptions()
{
local tab
tab=$(printf '\t')
local comp="$1"
local longest=$2
# Properly format the description string which follows a tab character if there is one
if [[ "$comp" == *$tab* ]]; then
desc=${comp#*$tab}
comp=${comp%%$tab*}
# $COLUMNS stores the current shell width.
# Remove an extra 4 because we add 2 spaces and 2 parentheses.
maxdesclength=$(( COLUMNS - longest - 4 ))
# Make sure we can fit a description of at least 8 characters
# if we are to align the descriptions.
if [[ $maxdesclength -gt 8 ]]; then
# Add the proper number of spaces to align the descriptions
for ((i = ${#comp} ; i < longest ; i++)); do
comp+=" "
done
else
# Don't pad the descriptions so we can fit more text after the completion
maxdesclength=$(( COLUMNS - ${#comp} - 4 ))
fi
# If there is enough space for any description text,
# truncate the descriptions that are too long for the shell width
if [ $maxdesclength -gt 0 ]; then
if [ ${#desc} -gt $maxdesclength ]; then
desc=${desc:0:$(( maxdesclength - 1 ))}
desc+="…"
fi
comp+=" ($desc)"
fi
fi
# Must use printf to escape all special characters
printf "%q" "${comp}"
}
__start_arduino-cli()
{
local cur prev words cword split
COMPREPLY=()
# Call _init_completion from the bash-completion package
# to prepare the arguments properly
if declare -F _init_completion >/dev/null 2>&1; then
_init_completion -n "=:" || return
else
__arduino-cli_init_completion -n "=:" || return
fi
__arduino-cli_debug
__arduino-cli_debug "========= starting completion logic =========="
__arduino-cli_debug "cur is ${cur}, words[*] is ${words[*]}, #words[#] is ${#words[#]}, cword is $cword"
# The user could have moved the cursor backwards on the command-line.
# We need to trigger completion from the $cword location, so we need
# to truncate the command-line ($words) up to the $cword location.
words=("${words[#]:0:$cword+1}")
__arduino-cli_debug "Truncated words[*]: ${words[*]},"
local out directive
__arduino-cli_get_completion_results
__arduino-cli_process_completion_results
}
if [[ $(type -t compopt) = "builtin" ]]; then
complete -o default -F __start_arduino-cli arduino-cli
else
complete -o default -o nospace -F __start_arduino-cli arduino-cli
fi
# ex: ts=4 sw=4 et filetype=sh
UPDATE2:
After I posted this I checked your links and after seeing the accepted answer here, I was thinking I am just old and forget that I just copied this code from that link, and it wasn't me who wrote it. Even the example used there is the same, but investigating the code further, it looks I did write this and used a different approach, and maybe this will help you understand what's going on. As I mentioned at the bottom UPDATE1 section: you need to tune the COMP variables then call the original function
Original:
I wrote an 'alias wrapper' script a couple of years ago.
The idea is to use the original bash completion with aliases even with parameters.
For example:
alias apti='apt-get install'
source alias-completion-wrapper _apt_get apti apt-get install
#here _apt_get is the original completion function
Now you can use tab to complete the package name after apti just like after apt-get install
#alias-completion-wrapper
#Example: . alias-completion-wrapper _apt_get apti apt-get install
comp_function_name="$1"
ali="$2"
shift 2
x="$#"
function_name=`echo _$# |tr ' ' _`
function="
function $function_name {
_completion_loader $1
(( COMP_CWORD += $# - 1 ))
COMP_WORDS=( $# \"\${COMP_WORDS[#]:1}\")
COMP_LINE=\"\${COMP_WORDS[#]}\"
let COMP_POINT=\${COMP_POINT}-${#ali}+${#x}
$comp_function_name
return 0
}"
eval "$function"
complete -F $function_name $ali
unset function function_name ali x
To be honest, I can't remember how it works and I didn't commented the script :)
But I think you will be able to tune this for your needs.
UPDATE1:
As I investigated the code a bit, it looks like the idea is to tune the COMP variables, then call the original function :)
UPDATE3:
I had some time, so the modification you need are:
This ${COMP_WORDS[#]} contains the current command line. ${COMP_WORDS[#]:1} cuts off the first word, which is originally the alias/command. As you want to use it after a parameter you have to cut off the parameter too.
COMP_WORDS=( $# \"\${COMP_WORDS[#]:2}\")
I don't see COMP_LINE and COMP_POINT in the other solution, but as I can recall without those, it doesn't worked well in certain circumstances. So I suppose you need:
COMP_LINE=\"\${COMP_WORDS[#]:1}\"
And here ${#ali} is the length of the command. You need to replace this with the length of your command with the parameter. eg,:"xcmd prm" -> 8 (count the space too)
let COMP_POINT=\${COMP_POINT}-${#ali}+${#x}
Not sure about (( COMP_CWORD += $# - 1 )) either remove the -1 or use -2 or leave it as it is :)
After the modifications, just change the eval to echo and remove the complete -F line. And source the script as described. This way it will echo the function what you can insert into your completion script.

Is there any way to get iTerm2 to color each new tab with a different color (using Oh My Zsh on Mac)?

I like having different colored tabs in iTerm2 to quickly know what each tab refers to. I can, of course, set them manually each time I open them, or download a script that will allow me to do that from the terminal itself (see pic for an example -- I've manually changed each tab's color). I was wondering whether there was a way (built in to iTerm2 or through an Oh My Zsh script) to simply have iTerm2 use a new (possibly random) color for the tab automatically on opening the tab? I have googled and can't find anything except a way to have SSH have a different tab color. Thanks for any help!
to support random color automaticly when using color without argument
add the following lines to ~/.profile or ~/.zshrc:
PRELINE="\r\033[A"
function random {
echo -e "\033]6;1;bg;red;brightness;$((1 + $RANDOM % 255))\a"$PRELINE
echo -e "\033]6;1;bg;green;brightness;$((1 + $RANDOM % 255))\a"$PRELINE
echo -e "\033]6;1;bg;blue;brightness;$((1 + $RANDOM % 255))\a"$PRELINE
}
function color {
case $1 in
green)
echo -e "\033]6;1;bg;red;brightness;57\a"$PRELINE
echo -e "\033]6;1;bg;green;brightness;197\a"$PRELINE
echo -e "\033]6;1;bg;blue;brightness;77\a"$PRELINE
;;
red)
echo -e "\033]6;1;bg;red;brightness;270\a"$PRELINE
echo -e "\033]6;1;bg;green;brightness;60\a"$PRELINE
echo -e "\033]6;1;bg;blue;brightness;83\a"$PRELINE
;;
orange)
echo -e "\033]6;1;bg;red;brightness;227\a"$PRELINE
echo -e "\033]6;1;bg;green;brightness;143\a"$PRELINE
echo -e "\033]6;1;bg;blue;brightness;10\a"$PRELINE
;;
*)
random
esac
}
#color #uncomment to enable automatically set random color when tab created
After each time a new iterm2 tab created, use command color to automaticly give it a new/random color.
if you want the iterm2-tab color set automatically whenever it is created, then .just add color to then end of .zshrc / .profile or just after the function color
Add this to your .bashrc/.zshrc/.whateverrc file to get a random tab color every time you open a new tab in iTerm2:
function tabcolor {
echo -n -e "\033]6;1;bg;red;brightness;$1\a"
echo -n -e "\033]6;1;bg;green;brightness;$2\a"
echo -n -e "\033]6;1;bg;blue;brightness;$3\a"
}
tabcolor $(jot -r 1 0 255) $(jot -r 1 0 255) $(jot -r 1 0 255)

Underlining to bash prompt only when pwd changed

I want to underline to my bash shell prompt(=PS1) only when a current directory is changed.
I tried this.
At .bashrc file I wrote
DIR_CHANGED=
function cd {
builtin cd "$#"
DIR_CHANGED=1
}
function dir_ul {
# if $DIR_CHANGED is 1, draw underline
if [ x == x$DIR_CHANGED ]; then echo -en '\033[0;34m'; else echo -en '\033[4;34m'; fi
export DIR_CHANGED=''
}
export PS1='$(dir_ul)\w$(tput sgr0)$ '
But not worked.
How do I fix?
I hate to say it, but I'd call that a bash bug. Here's a workaround: use PROMPT_COMMAND to
copy $DIR_CHANGED and reset it, and in dir_ul refer to that saved copy. Minimal changes:
function dir_ul {
if [ x == x$DIR2 ]; then echo -en '\033[0;34m'; else echo -en '\033[4;34m'; fi
}
PROMPT_COMMAND='DIR2=$DIR_CHANGED;DIR_CHANGED='
Test this in your ~/.bashrc with a second shell:
PREV="$PWD"
PROMPT_COMMAND='[[ $PREV != $PWD ]] && PS1="$(tput smul)\w$(tput rmul)$ " && PREV="$PWD" || PS1="$(tput rmul)\w$ "'
The $(dir_ul) gets evaluated at the instance you set the PS1 variable, rather than being continuously updated.

Run current bash script in background

Usually I add "&" character to start my process in backgroud, exemple :
user#pc:~$ my_script &
But how can I make it in background without "&" character ?
#!/bin/bash
#What can I add here to hide current process ($$) and to release focus ?
start_server()
{
#my script here with infinite loop ...
}
Thanks guys.
#!/bin/bash
if [[ "$1" != "--nodaemon" ]]; then
( "$0" --nodaemon "$#" </dev/null &>/dev/null & )
else
shift
fi
#...rest of script
What this does is check to see if its first argument is "--nodaemon", and if so fire itself ("$0") off in the background with the argument "--nodaemon", which'll prevent it from trying to re-background itself in a sort of infinite loop.
Note that putting this as the first thing in the script will make it always run itself in the background. If it only needs to drop into the background under certain conditions (e.g. when run with the argument "start"), you'd have to adjust this accordingly. Maybe something like this:
#!/bin/bash
start_server()
{
#my script here with infinite loop ...
}
if [[ "$1" = "start" ]]; then
( "$0" start-nodaemon </dev/null &>/dev/null & )
elif [[ "$1" = "start-nodaemon" ]]; then
start_server
elif #.....

Setting a variable within a bash PS1

I'm trying to customize my bash prompt and I'm having trouble with a few conditionals.
My current PS1 looks like this.
export PS1="\
$PS1USERCOLOR\u\
$COLOR_WHITE#\
$COLOR_GREEN\h\
$COLOR_WHITE:\
$COLOR_YELLOW\W\
\`if type parse_git_branch > /dev/null 2>&1; then parse_git_branch; fi\`\
\`if [ \$? = 0 ]; then echo -e '$COLOR_WHITE'; else echo -e '$COLOR_RED'; fi\`\$\
$COLOR_WHITE"
The first 6 lines just set regular PS1 stuff.
Line 7 then calls a function to display the current git branch and status if applicable.
Line 8 then tests the return code of the previous command and changes the colour of the $ on the end.
Line 9 sets the prompt back to white ready for the user's command.
However line 8 is responding to the return code from line 7's function and not the previous command as I first expected.
I've tried moving line 8 before line 7 and eveything works as it should. But I don't want line 8 before line 7, the $ must be on the end.
I've tried setting a variable earlier on to be the value of $? and then testing that variable like so
export PS1="\
\`RETURN=\$?\`\
$PS1USERCOLOR\u\
$COLOR_WHITE#\
$COLOR_GREEN\h\
$COLOR_WHITE:\
$COLOR_YELLOW\W\
\`if type parse_git_branch > /dev/null 2>&1; then parse_git_branch; fi\`\
\`if [ \$RETURN = 0 ]; then echo -e '$COLOR_WHITE'; else echo -e '$COLOR_RED'; fi\`\$\
$COLOR_WHITE"
But this doesn't work.
Does anybody have any idea how to solve my problem?
The proper way is to use PROMPT_COMMAND like so:
prompt_cmd () {
LAST_STATUS=$?
PS1="$PS1USERCOLOR\u"
PS1+="$COLOR_WHITE#"
PS1+="$COLOR_GREEN\h"
PS1+="$COLOR_WHITE:"
PS1+="$COLOR_YELLOW\W"
if type parse_git_branch > /dev/null 2>&1; then
PS1+=$(parse_git_branch)
fi
if [[ $LAST_STATUS = 0 ]]; then
PS1+="$COLOR_WHITE"
else
PS1+="$COLOR_RED"
fi
PS1+='\$'
PS1+="$COLOR_WHITE"
}
Since PROMPT_COMMAND is evaluated prior to each prompt, you simply execute code that sets PS1 they way you like for each prompt instance, rather than trying to embed deferred logic in the string itself.
A couple of notes:
You must save $? in the first line of the code, before the value you want is overwritten.
I use double quotes for most of the steps, except for \$; you could use PS1+="\\\$" if you like.
The standard solution to this problem is to make use of the bash environment variable PROMPT_COMMAND. If you set this variable to the name of a shell function, said function will be executed before each bash prompt is shown. Then, inside said function, you can set up whatever variables you want. Here's how I do almost exactly what you're looking for in my .bashrc:
titlebar_str='\[\e]0;\u#\h: \w\a\]'
time_str='\[\e[0;36m\]\t'
host_str='\[\e[1;32m\]\h'
cwd_str='\[\e[0;33m\]$MYDIR'
git_str='\[\e[1;37m\]`/usr/bin/git branch --no-color 2> /dev/null | /bin/grep -m 1 ^\* | /bin/sed -e "s/\* \(.*\)/ [\1]/"`\[\e[0m\]'
dolr_str='\[\e[0;`[ $lastStatus -eq 0 ] && echo 32 || echo 31`m\]\$ \[\e[0m\]'
export PS1="$titlebar_str$time_str $host_str $cwd_str$git_str$dolr_str"
function prompt_func {
# Capture the exit status currently in existence so we don't overwrite it with
# any operations performed here.
lastStatus=$?
# ... run some other commands (which will have their own return codes) to set MYDIR
}
export PROMPT_COMMAND=prompt_func
Now bash will run prompt_func before displaying each new prompt. The exit status of the preceding command is captured in lastStatus. Because git_str, dolr_str, etc. are defined with single quotes, the variables (including lastStatus) and commands inside them are then re-evaluated when bash dereferences PS1.
Solved it!
I need to use the PROMPT_COMMAND variable to set the RETURN variable. PROMPT_COMMAND is a command which is called before PS1 is loaded.
My script now looks like
PROMPT_COMMAND='RETURN=$?'
export PS1="\
$PS1USERCOLOR\u\
$COLOR_WHITE#\
$COLOR_GREEN\h\
$COLOR_WHITE:\
$COLOR_YELLOW\W\
\`if type parse_git_branch > /dev/null 2>&1; then parse_git_branch; fi\`\
\`if [[ \$RETURN = 0 ]]; then echo -e '$COLOR_WHITE'; else echo -e '$COLOR_RED'; fi\`\$\
$COLOR_WHITE"

Resources