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'

Attachment: signature.asc
Description: This is a digitally signed message part

Reply via email to