Search code examples
bashcolorssyntax-highlightinghighlightpygmentize

How to syntax highlight a bash output (some help information)?


Very often I get some help information with the --help flag of a command, which gives the output at the terminal like:

$ vmtkimagereader --help

Creating vmtkImageReader instance.
Automatic piping vmtkimagereader
Parsing options vmtkimagereader

vmtkimagereader : read an image and stores it in a vtkImageData object
  Input arguments:
   -id Id (str,1); default=0: script id
   -handle Self (self,1): handle to self
   -disabled Disabled (bool,1); default=0: disable execution and
     piping

I want to syntax highlight the output like the upper half of the link (sorry that I can only post 1 link). I have tried highlight and pygmentize. However, highlight needs to specify a syntax, and pygmentize rendered the output as a wrong style (in the lower half of the link).

I'd like to know if there is a method to make the syntax highlight like this. Do I need to specify a style for pygmentize? Or do I have to turn to another solution?

Thanks!


Solution

  • ANSI escape strings

    Using ANSI escape sequences to achieve what you want, you can create a format string (represented by prepended \e[ and appended m) where 38;5;{0..255} is the 256-color of the text (0..255 being the range of available color codes), and 48;5;{0..255} is the 256-color of background. E.g.,

    echo -e "\e[38;5;0;48;5;255mText\e[0m"
    

    will print black text (color code 0) with a white background (color code 255). Note with the echo command it requires the extended mode (toggled by the -e flag) to interpret the ANSI escape string.

    Note the trailing \e[0m to unset the coloring, otherwise all text printed after this command with echo will retain the format. \e[0m resets it.

    Note an interesting use case that causes an error also. Placing an exclamation point before the ending \e[0m causes this output:

    nick@nick-lt:~$ echo -e "\e[38;5;0;48;5;255mText!\e[0m"
    bash: !\e[0m: event not found
    

    That's because ! is part of string expansion for Bash. See more on this SO question here. To make that work as expected we need to do:

    echo -e "\e[38;5;0;48;5;255mText"'!'"\e[0m"
    

    as single-quotes do not get expanded.


    How to print every available color using ANSI escape sequences.

    Save these in a file called color-functions.sh:

    function color_list_text() {
        # First paramter can be an optional background color
        local BGCOLOR="$1"
    
        COLOR=
        # Loop through the number range 0 to 255
        for COLOR in {0..255}; do
            local BGCOLORFORM=""
            # If our first parameter has a value, then create a background
            # format in ANSI escape sequence, assign to $BGCOLORFORM
            [[ -z "$BGCOLOR" ]] && BGCOLORFORM="48;5;${BGCOLOR};"
    
            # Create the whole ANSI escape sequence, assign to $TEXTFORM
            local TEXTFORM="${BGCOLORFORM}38;5;${COLOR}m"
    
            echo -en "\e[${TEXTFORM} ${COLOR}\t\e[0m"
    
            [[ $(( (COLOR + 1) % 16 )) -eq 0 ]] && echo
        done
    
        return 0
    }
    
    function color_list_text_backgrounds() {
        local TEXTCOLOR="$1"
    
        local COLOR
        for COLOR in {0..255}; do
            local TEXTCOLORFORM=""
            [[ -z "$TEXTCOLOR" ]] && TEXTCOLORFORM="38;5;${TEXTCOLOR};"
    
            local TEXTFORM="${TEXTCOLORFORM}48;5;${COLOR}m"
    
            echo -en "\e[${TEXTFORM} ${COLOR}\t\e[0m"
    
            [[ $(( (COLOR + 1) % 16 )) -eq 0 ]] && echo
        done
    
        return 0
    }
    

    Then, in another file, call the functions after you've source'd them:

    source color-functions.sh
    
    # Loops through and prints all ANSI escape sequence's available text colors
    color_list_text
    # Loops through and prints all ANSI escape sequence's available text backgrounds
    color_list_backgrounds
    

    Here's a function that does both... But I think it's overkill because the output is far too large (256 * 256 = 2^16 combinations will be outputted):

    function color_list_text_and_backgrounds() {
        local BG
        for BG in {0..255}; do
            local TEXT
            for TEXT in {0..255}; do
                echo -en "\e[38;5;${TEXT};48;5;${BG}m ${TEXT}\t\e[0m"
    
                if [[ $(( (TEXT + 1) % 8 )) -eq 0 ]]; then
                    if [[ $(( ((TEXT + 1) / 8) )) -eq 16 ]]; then
                        local INVBG=$(( (BG + 128) % 256 ))
                        echo -en "\e[38;5;${INVBG};48;5;${BG}m ${BG}\t\e[0m"
                    else
                        echo -en "\e[48;5;${BG}m\t\e[0m"
                    fi
    
                    echo
                fi
            done
        done
    
        return 0
    }
    

    Your use case

    To color certain things certain colors, we can use egrep -i (-i flag is case-insensitive) and the GREP_COLOR variable:

    echo "Some string to color" | \
        GREP_COLOR='38;5;200' egrep -i --color=always 'some' | \
        GREP_COLOR='38;5;100' egrep -i --color=always 'string|color'
    

    Or we could be real clever and functionise this:

    color_text_match() {
        MATCHSTRING="$1"
        COLOR="$2"
    
        [[ -z "$MATCHSTRING" ]] && echo "color_text_match: No color specifies." 
        [[ -z "$COLOR" ]] && COLOR="214"   # Default orange
    
        GREP_COLOR="38;5;$COLOR" egrep -i --color=always "$MATCHSTRING"
    }
    

    Then:

    echo "Some string to color" | \
        color_text_match "Some" | \
        color_text_match "string" | \
        color_text_match "to" "226"