Is there an efficient way to colorize text in a bash prompt without two calls?

人走茶凉 提交于 2019-12-11 05:28:40

问题


Efficient way to colorize text in a bash prompt without two calls?

There are plenty of resources around the place about customizing PS1. The critical points here are:

  • It's possible to call a custom function to generate text, resulting in custom text
  • It's possible for such a function to emit custom color codes
  • Non-printing text (like color codes) needs to be marked to make word wrapping work
  • It's NOT possible for a custom function to do that marking.

I may be wrong on the last point, and if there's a way, that would perfectly solve this.

Here's a simplified and somewhat contrived example that's topologically similar, if that makes sense, to the one I'm tinkering with. I have an external command (call it generate_text) which produces on stdout either "OK" or some one-word message. It may also emit nothing at all. Those three states should be shown in the prompt: if it emits nothing, leave the prompt exactly as it is (user@hostname:path$); if it emits OK, put a green "OK " before the prompt; if anything else, put that text in red.

My current solution is to have a custom function that invokes generate_text and either emits its text or emits a color code based on it, and then call that function twice:

generate_prompt()
{
    txt=`generate_text`
    [ -z "$txt" ] && exit # The empty case. Produce nothing.
    $1 && echo "##$msg## " || case $msg in
        'OK') echo -e '\e[1;32m'; ;; # Green for OK
        *) echo -e "\e[1;31m"; ;; # Red for anything else
    esac
}
PS1='\[$(generate_prompt false)\]$(generate_prompt true)\[\e[0m\]'$PS1

This means I have to call generate_text twice and assume that they'll return the same string (which will normally be the case, but it's theoretically possible that state could change between the two invocations). This strikes me as somewhat wasteful. Is there a convenient way to emit both a non-printing color code and a piece of text from within the same function?


回答1:


You can use PROMPT_COMMAND to calculate all required values, and then using them in your prompt:

generate_prompt() {
  color="" message=""
  txt=$(generate_text)
  [[ -z $txt ]] && return

  message="##$txt##"
  [[ $txt == OK ]] && color=$'\e[1;32m' || color=$'\e[1;31m'
}
PROMPT_COMMAND=generate_prompt
PS1='\[$color\]$message\[\e[0m\]'$PS1

Note that PROMPT_COMMAND is sometimes already configured to set the xterm title, in which case you can append to it instead.




回答2:


This is undocumented so probably not a good solution but appears to work. It uses internal knowledge of the bash prompting which indicates that text between \001 and \002 characters do not count towards the number of visible characters in the prompt string.

generate_prompt()
{
    local txt=$(generate_text)
    case "$txt" in
        '') ;;
        OK) echo -e "\001\e[1;32m\002 $txt \001\e[0m\002"; ;; # Green for OK
         *) echo -e "\001\e[1;31m\002 $txt \001\e[0m\002"; ;; # Red for anything else
    esac
}
PS1='$(generate_prompt)'$PS1

The bash source which expands the prompt string contain the following comment which was used to formulate the above solution:

/* Current implementation:
    \001 (^A) start non-visible characters
    \002 (^B) end non-visible characters
   all characters except \001 and \002 (following a \001) are copied to
   the returned string; all characters except those between \001 and
   \002 are assumed to be `visible'. */ 


来源:https://stackoverflow.com/questions/14636234/is-there-an-efficient-way-to-colorize-text-in-a-bash-prompt-without-two-calls

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!