Hi folks, I wanted a way in bash to print the status of the last command I ran, including information about each command in a pipe and about signals.
I noticed that bash does not have the ability to display a command result, so I came up with the attached prompt configuration. I am hoping that folks here are interested in fixing the things that I found that I had to workaround: * The command number variable $# is not available at the time that the PROMPT_COMMAND is run, only when $PS1 is evaluated. * There isn't an easy way to detect no command run yet, syntax errors, Ctrl+C, Enter etc versus a command being run and exiting * Sometimes PIPESTATUS isn't reset even though $? has been reset I am hoping that folks here have suggestions or fixes for the things that I found I wasn't able to do: * Determine if a command exited normally or due to a signal, even if it exited normally with an exit code greater than 128. * Have the PIPESTATUS be correct after a Ctrl+Z. * Differentiate between Ctrl+C Ctrl+C and Ctrl+C Enter. * Disable the status display after tab completion output. * Disable the status on the first line of the terminal after using the the terminal clearing functionality of GNOME Terminal. I am hoping that folks here are interested in adding native support for displaying command status, possibly like this: * a PS5 variable that would be evaluated and displayed before the display of the PS1 prompt and after every exit of a command or Ctrl+C/Ctrl+Z/etc but not after syntax errors * a STATUS_COMMAND variable that would be run before evaluation of the PS5 variable to support customising it dynamically -- bye, pabs https://bonedaddy.net/pabs3/
__p_exit_codes_to_SIGs () {
sigcolour="$1" failcolour="$2" successcolour="$3" ; shift 3
local -n exit_status=$1 ; shift
local -n exit_colour=$1 ; shift
local exit_codes=( "$@" )
local exit_code
local i=0
for exit_code in "${exit_codes[@]}" ; do
exit_status[$i]="${exit_code}"
# FIXME: process signals instead of exit codes > 128
if [ "$exit_code" -gt 128 ] ; then
local signal=$((exit_code-128))
signal="SIG$(kill -l "$signal" 2> /dev/null)" ||
signal=""
if [ -n "$signal" ] ; then
exit_status[$i]="${signal}"
exit_colour[$i]="${sigcolour}"
else
exit_colour[$i]="${failcolour}"
fi
elif [ "$exit_code" -ne 0 ] ; then
exit_colour[$i]="${failcolour}"
else
exit_colour[$i]="${successcolour}"
fi
i=$((i+1))
done
}
__prompt_command () {
local exit_code="$?" pipe_exit_codes=( "${PIPESTATUS[@]}" )
local s='\['
local e='\]'
local cr='\r'
local lf='\n'
local user='\u'
local host='\h'
local cwd='\w'
local bel='\a'
local reset="$(tput sgr0)"
local reset_colour="$(tput op)$(tput oc)"
local bold="$(tput bold)"
local flash="$(tput flash)"
local black="$(tput setaf 0)"
local red="$(tput setaf 1)"
local green="$(tput setaf 2)"
local yellow="$(tput setaf 3)"
local blue="$(tput setaf 4)"
local magenta="$(tput setaf 5)"
local cyan="$(tput setaf 6)"
local white="$(tput setaf 7)"
# Save the status colours without terminal start/end sequences
# since the status display uses those start/end sequences already
# since they have to be in PS1 rather than inserted at PS1 evaluation
local sigcolour="$blue"
local failcolour="$red"
local successcolour=""
# Save the reset sequences without terminal start/end sequences
# since the status display uses those start/end sequences already
# since they have to be in PS1 rather than inserted at PS1 evaluation
__p_reset="$reset"
# Add terminal start/end sequences to shorten colour declarations
reset="${s}${reset}${e}"
reset_colour="${s}${reset_colour}${e}"
bold="${s}${bold}${e}"
black="${s}${black}${e}"
red="${s}${red}${e}"
green="${s}${green}${e}"
yellow="${s}${yellow}${e}"
blue="${s}${blue}${e}"
magenta="${s}${magenta}${e}"
cyan="${s}${cyan}${e}"
white="${s}${white}${e}"
# Define colours for the chroot, user, host and cwd
local chrootcolour="${red}${bold}"
local usercolour="${green}${bold}"
local hostcolour="${green}${bold}"
# modulate colors based on host and user to differentiate shells
#local usercolor="${s}$(tput setaf $(((1+$(cksum <<<"$USER"|cut -f 1 -d
' ')) %7 +1 )))${e}"
#local hostcolour="${s}$(tput setaf $(((4+$(cksum <<<"$HOSTNAME"|cut -f
1 -d ' ')) %7 +1 )))${e}"
local cwdcolour="${blue}${bold}"
# Define various separators
local status_indicator_primary_status_sep=' '
local primary_status_pipe_status_sep=' '
local pipe_status_start='('
local pipe_status_sep=' | '
local pipe_status_end=')'
local status_chroot_sep="${lf}"
local chroot_user_sep=''
local user_host_sep='@'
local host_cwd_sep=' '
local cwd_cmd_sep=' \$ '
# Define the status indicator string
if [ "$TERM" = xterm-256color ] ; then
local status_indicator='⭲'
else
local status_indicator='->|'
fi
# Define the printf used for the git prompt fragment
# the empty value tells git to use the default
local git_printf=''
# Define if command success should be shown
local show_success=0
# Used when showing/hiding status parts at PS1 evaluation time
__p_print=9999
__p_pipe_print=9999
# Calculate the text and colour of the primary status
__p_primary_status=( )
__p_primary_colour=( )
__p_exit_codes_to_SIGs "$sigcolour" "$failcolour" "$successcolour"
__p_primary_status __p_primary_colour "$exit_code"
# Calculate the colour of the status prefix
__p_status_prefix_colour="${__p_primary_colour[0]}"
# Calculate the text and colour of the pipe statuses
__p_pipe_status=( )
__p_pipe_colour=( )
__p_exit_codes_to_SIGs "$sigcolour" "$failcolour" "$successcolour"
__p_pipe_status __p_pipe_colour "${pipe_exit_codes[@]}"
# Disable the status display when asked to not display success
local pipe_exit_code_sum=0
local pipe_exit_code
for pipe_exit_code in "${pipe_exit_codes[@]}"; do
((pipe_exit_code_sum+=pipe_exit_code))
done
if [ "$show_success" -eq 0 -a "$exit_code" -eq 0 -a
"$pipe_exit_code_sum" -eq 0 ] ; then
__p_print=0
__p_pipe_print=0
fi
# Pass the status prefix to PS1 evaluation time
__p_status_prefix="${status_indicator}${status_indicator_primary_status_sep}"
local
status_prefix="${s}"'${__p_status_prefix_colour:0:((__p_print*__ps1_print))}'"${e}"
status_prefix="${status_prefix}"'${__p_status_prefix:0:((__p_print*__ps1_print))}'
status_prefix="${status_prefix}${s}"'${__p_reset:0:((__p_print*__ps1_print))}'"${e}"
# Pass the primary status to PS1 evaluation time
local
primary_status="${s}"'${__p_primary_colour[0]:0:((__p_print*__ps1_print))}'"${e}"
primary_status="${primary_status}"'${__p_primary_status[0]:0:((__p_print*__ps1_print))}'
primary_status="${primary_status}${s}"'${__p_reset:0:((__p_print*__ps1_print))}'"${e}"
# Disable the pipe statuses when identical to the primary status
# FIXME: disable pipe statuses after Ctrl+Z
if [ "${#pipe_exit_codes[@]}" -eq 1 -a "$exit_code" -eq
"${pipe_exit_codes[0]}" ] ; then
__p_pipe_print=0
primary_status_pipe_status_sep=
pipe_status_start=
__p_pipe_status=()
__p_pipe_colour=()
pipe_status_sep=
pipe_status_end=
fi
# Pass the separator between the primary and pipe statuses to PS1
evaluation time
__p_primary_status_pipe_status_sep="$primary_status_pipe_status_sep"
primary_status_pipe_status_sep='${__p_primary_status_pipe_status_sep:0:((__p_pipe_print*__ps1_print))}'
# Pass the pipe statuses start to PS1 evaluation time
__p_pipe_status_start="$pipe_status_start"
pipe_status_start='${__p_pipe_status_start:0:((__p_pipe_print*__ps1_print))}'
# Pass the pipe statuses to PS1 evaluation time
local pipe_statuses=( )
local pipe_status_count=${#__p_pipe_status[@]}
__p_pipe_status_sep=( )
local i
for (( i=0; i < pipe_status_count; i++ )) ; do
pipe_statuses[$i]="${s}"'${__p_pipe_colour['"$i"']:0:((__p_pipe_print*__ps1_print))}'"${e}"
pipe_statuses[$i]="${pipe_statuses[$i]}"'${__p_pipe_status['"$i"']:0:((__p_pipe_print*__ps1_print))}'
pipe_statuses[$i]="${pipe_statuses[$i]}${s}"'${__p_reset:0:((__p_pipe_print*__ps1_print))}'"${e}"
# Append the separator except for the last item
if [ "$i" -lt "$((pipe_status_count-1))" ] ; then
__p_pipe_status_sep[$i]="$pipe_status_sep"
else
__p_pipe_status_sep[$i]=""
fi
pipe_statuses[$i]="${pipe_statuses[$i]}"'${__p_pipe_status_sep['"$i"']:0:((__p_pipe_print*__ps1_print))}'
done
# Join the pipe statuses
local ifs="$IFS"
IFS=""
local pipe_status="${pipe_statuses[*]}"
IFS="$ifs"
unset ifs
# Pass the pipe statuses end to PS1 evaluation time
__p_pipe_status_end="$pipe_status_end"
pipe_status_end='${__p_pipe_status_end:0:((__p_pipe_print*__ps1_print))}'
# Pass the separator between the status and chroot to PS1 evaluation
time
printf -v __p_status_chroot_sep "$status_chroot_sep"
status_chroot_sep='${__p_status_chroot_sep:0:((__p_print*__ps1_print))}'
# Detect when there was not a syntax error or Ctrl+C
PS0='${IFS:0:0*((__p_is_cmd=1, 0))}'
# Pass the status print decision code to PS1 evaluation time
local init='${IFS:0:0*(('
# Do not print the status at the start or after pressing enter
# or when there are two syntax errors or Ctrl+C in a row
# FIXME: differentiate between Ctrl+C Ctrl+C and Ctrl+C Enter
# FIXME: disable the status after tab completion output
# FIXME: disable the status on the first line (after terminal clearing)
init="${init}"'__ps1_print=(__p_last_cmd_no!=0 && (__p_last_cmd_no!=\#
|| (__p_is_cmd!=1 && __p_last_status!=$?))), '
# Disable printing the pipe status for syntax error or Ctrl+C (doesn't
update PIPESTATUS)
init="${init}"'__p_pipe_print=(__p_is_cmd==1 ? __p_pipe_print : 0), '
# Reset the commandness as syntax errors and Ctrl+C don't do that
init="${init}"'__p_is_cmd=0, '
# Store the last command number and last status for next evaluation
init="${init}"'__p_last_cmd_no=\#, __p_last_status=$?, 0))}'
# Prepare the window title, status, chroot for the prompt
local
title="${s}\\e]0;${chroot}${chroot_user_sep}${user}${user_host_sep}${host}${host_cwd_sep}${cwd}${bel}${e}"
local
status="${status_prefix}${primary_status}${primary_status_pipe_status_sep}${pipe_status_start}${pipe_status}${pipe_status_end}"
local chroot="${debian_chroot:+($debian_chroot)}"
# Colourise the chroot, user, host, cwd
chroot="${chrootcolour}${chroot}${reset}"
user="${usercolour}${user}${reset}"
host="${hostcolour}${host}${reset}"
cwd="${cwdcolour}${cwd}${reset}"
# Prepare the pre/post text
local
pre="${status}${status_chroot_sep}${chroot}${chroot_user_sep}${user}${user_host_sep}${host}${host_cwd_sep}${cwd}${pre}"
local post="$cwd_cmd_sep"
# Only set the window title in supported terminals
case "$TERM" in
xterm*|rxvt*)
pre="${title}${pre}"
;;
*)
;;
esac
# Reset to avoid leaking any non-terminated colours or highlighting
from the command
pre="${reset}${reset_colour}${pre}"
# Put the newness calculation at the very start of the prompt
# so that it can be used to show/hide any info in the prompt
pre="${init}${pre}"
# Set PS1 to the git PS1 plus prepared pre/post text
__git_ps1 "$pre" "$post" "$git_printf"
}
PROMPT_COMMAND='__prompt_command'
signature.asc
Description: This is a digitally signed message part
