0

PROMPT_COMAND runs slow. Code optimization needed.

I use bash builtin PROMPT_COMMAND to customize PS1. Too much echo | grep commands were executed to get colorful git branch indication.The prompt runs much slower. Any ideas about optimazation?

function git_branch {                                                            
    branch="`git branch 2>/dev/null | grep "^\*" | sed -e "s/^\*\ //"`"          
    if [ "${branch}" != "" ];then                                                
        if [ "${branch}" = "(no branch)" ];then                                  
            branch="(`git rev-parse --short HEAD`...)"                           
        fi                                                                       
        echo "$branch"                                                           
    fi                                                                           
}                                                                                

# display last two dentry                                                        
function get_curdir {                                                            
    dir=$(pwd);                                                                  
    last_dirent=${dir##*/}                                                       
    dir=${dir%/*}                                                                
    lastbutone_dirent=${dir##*/}                                                 
    echo -n "${lastbutone_dirent}/${last_dirent}"                                
}                                                                                

# colours                                                                        
RED="\[\033[1;31m\]"                                                             
GREEN="\[\033[1;32m\]"                                                           
BROWN="\[\033[0;33m\]"                                                           
BLUE="\[\033[1;34m\]"                                                            
PURPLE="\[\033[1;35m\]"                                                          
CYAN="\[\033[1;36m\]"                                                            
GRAY="\[\033[0;37m\]"                                                            
LIGHT_RED="\[\033[0;31m\]"                                                       
LIGHT_GREEN="\[\033[1;32m\]"                                                                                                                                                
LIGHT_BLUE="\[\033[0;34m\]"                                                      
YELLOW="\[\033[1;33m\]"                                                          
LIGHT_PURPLE="\[\033[1;35m\]"                                                    
LIGHT_CYAN="\[\033[1;36m\]"                                                      
LIGHT_GRAY="\[\033[0;37m\]"                                                      
WHITE="\[\033[1;37m\]"                                                           
RESTORE="\[\033[0m\]" #0m restores to the terminal's default colour

function prompt_command {                                                        
    RET=$?                                                                       
    PS1="${CYAN}[${RESTORE}${GREEN}\u${RESTORE} "                                
    PS1+="${BLUE}$(get_curdir)${RESTORE}${CYAN}]${RESTORE}"                      

    BRANCH=$(git_branch)                                                         
    if [ "${BRANCH}" != "" ]; then                                               
        STATUS="$(git status -s 2>/dev/null)"                                    
        if echo "${STATUS}" | grep -e "??"; then                                 
            PARENTHESES_COLOR=${RED}                                             
        else                                                                     
            PARENTHESES_COLOR=${GREEN}                                           
        fi >/dev/null                                                            
        if [ "${STATUS}" = "" ]; then                                            
            BRANCH_COLOR=${GREEN}                                                
        else                                                                     
            if echo "${STATUS}" | grep -e "^[ M]M"; then                         
                if echo "${STATUS}" | grep -e "^M[ M]"; then                     
                    BRANCH_COLOR=${PURPLE}                                       
                else                                                             
                    BRANCH_COLOR=${RED}                                          
                fi                                                               
            elif echo "${STATUS}" | grep -e "^M "; then                          
                BRANCH_COLOR=${YELLOW}                                           
            fi                                                                   
        fi >/dev/null                                                            
        PS1+="${PARENTHESES_COLOR}(${RESTORE}${BRANCH_COLOR}$(git_branch)${RESTORE}${PARENTHESES_COLOR})${RESTORE}"                                                         
    fi                                                                           

    if [ "$RET" = "0" ]; then                                                    
        PS1+=" ${CYAN}->${RESTORE} "                                             
    else                                                                         
        PS1+=" ${RED}->${RESTORE} "                                              
    fi                                                                           
}                                                                                
export PROMPT_COMMAND=prompt_command

update

I run a simple test in some dir where a git repo resides and use time to get time cost.

for ((i=0;i<10000;i++));do                                                       
    prompt_command;                                                              
done

Performance of the original version above is:

real    3m4.567s
user    1m32.698s
sys     3m2.495s

Then I change [] to [[ ]], diff like this:

17,18c17,18
<     if [ "${branch}" != "" ];then
<               if [ "${branch}" = "(no branch)" ];then
---
>     if [[ "${branch}" != "" ]];then
>               if [[ "${branch}" == "(no branch)" ]];then
58c58
<       if [ "${BRANCH}" != "" ]; then
---
>       if [[ "${BRANCH}" != "" ]]; then
65c65
<               if [ "${STATUS}" = "" ]; then
---
>               if [[ "${STATUS}" == "" ]]; then
81c81
<       if [ "$RET" = "0" ]; then
---
>       if [[ "$RET" == "0" ]]; then

Performance get a little worse

real    3m7.690s
user    1m30.717s
sys     3m6.676s

So, [] doesn't matter. But builtin regex helps a lot. When I changes to the following and replace $(pwd) with $PWD

    if [ "${BRANCH}" != "" ]; then                                                  
        regex_untracked=".*^\?\?.*"                                                 
        regex_staged=".*^M[ M].*"                                                   
        regex_modified=".*^[ M]M.*"                                                 
        STATUS="$(git status -s 2>/dev/null)"                                       
        if [[ ${STATUS} =~ $regex_untracked ]]; then                                
            PARENTHESES_COLOR=${RED}                                                
        else                                                                        
            PARENTHESES_COLOR=${GREEN}                                              
        fi >/dev/null                                                               
        if [[ ${STATUS} =~ $regex_modified ]]; then                                 
            if [[ ${STATUS} =~ $regex_staged ]]; then                               
                BRANCH_COLOR=${PURPLE}                                              
            else                                                                    
                BRANCH_COLOR=${RED}                                                 
            fi                                                                      
        elif [[ ${STATUS} =~ $regex_staged ]]; then                                 
            BRANCH_COLOR=${YELLOW}                                                  
        else                                                                        
            BRANCH_COLOR=${GREEN}                                                                                                                                           
        fi >/dev/null                                                               
        PS1+="${PARENTHESES_COLOR}(${RESTORE}${BRANCH_COLOR}$(git_branch)${RESTORE}${PARENTHESES_COLOR})${RESTORE}"
    fi

Time consumed decreases:

real    2m15.534s
user    1m1.036s
sys     2m15.043s

By the way, without this colorful branch feature, performance is

real    1m0.478s
user    0m29.499s
sys     1m1.411s
  • You can factorize `echo | grep | sed` with `git branch 2>/dev/null | sed -ne "s/^\*\ //p"` – Zelnes Jul 23 '19 at 12:49
  • Are you sure this is a bash issue? Maybe the real problem are the Git commands? – lxg Jul 23 '19 at 12:55
  • `$(pwd)` should be `"$PWD"` whenever performance matters. – Charles Duffy Jul 23 '19 at 12:56
  • It's not likely to make a noticeable difference here, but double square brackets evaluate a lot faster than single ones. The former is a shell keyword while the latter is a shell builtin. This would probably only make a real difference in a loop. It's likely that spawning a bunch of external processes is the main culprit as others have pointed out. – Dennis Williamson Jul 23 '19 at 23:47
  • @DennisWilliamson, `[` is still a builtin in bash, so it doesn't need a fork; `[[` just gets you specialized parser-level behavior. – Charles Duffy Jul 24 '19 at 02:53
  • 1
    @CharlesDuffy: Yes, I know and I said as much. But apparently `[[` evaluates about a third faster, at least in some circumstances: compare `time for ((i=0; i<10000000; i++)); do [[ "$i" = 1000 ]]; done` to the same thing with single square brackets. For me, that loop with `[[` executes in about 24 seconds, but it takes about 36 seconds with `[`. – Dennis Williamson Jul 24 '19 at 04:18

1 Answers1

1

bash has its own built-in regular expression matching. For example, replace

echo "${STATUS}" | grep -e "^[ M]M"

with

regex="^[ M]M"
[[ $STATUS =~ $regex ]]
chepner
  • 497,756
  • 71
  • 530
  • 681