How to set font color for STDOUT and STDERR

Create a function in a bash shell or script:

color()(set -o pipefail;"$@" 2>&1>&3|sed $'s,.*,\e[31m&\e[m,'>&2)3>&1

Use it like this:

$ color command -program -args

It will show the command's stderr in red.

Keep reading for an explanation of how it works. There are some interesting features demonstrated by this command.

  • color()... — Creates a bash function called color.
  • set -o pipefail — This is a shell option that preserves the error return code of a command whose output is piped into another command. This is done in a subshell, which is created by the parentheses, so as not to change the pipefail option in the outer shell.
  • "$@" — Executes the arguments to the function as a new command. "$@" is equivalent to "$1" "$2" ...
  • 2>&1 — Redirects the stderr of the command to stdout so that it becomes sed's stdin.
  • >&3 — Shorthand for 1>&3, this redirects stdout to a new temporary file descriptor 3. 3 gets routed back into stdout later.
  • sed ... — Because of the redirects above, sed's stdin is the stderr of the executed command. Its function is to surround each line with color codes.
  • $'...' A bash construct that causes it to understand backslash-escaped characters
  • .* — Matches the entire line.
  • \e[31m — The ANSI escape sequence that causes the following characters to be red
  • & — The sed replace character that expands to the entire matched string (the entire line in this case).
  • \e[m — The ANSI escape sequence that resets the color.
  • >&2 — Shorthand for 1>&2, this redirects sed's stdout to stderr.
  • 3>&1 — Redirects the temporary file descriptor 3 back into stdout.

Here's a hack that I thought of and it seems to work:

Given the following aliases for readability:

alias blue='echo -en "\033[36m"'
alias red='echo -en "\033[31m"'
alias formatOutput='while read line; do blue; echo $line; red; done'

Now, you need to first set the font color in your terminal to red (as the default, which will be used for stderr). Then, run your command and pipe the stdout through formatOutput defined above (which simply prints each line as blue and then resets the font color to red):

shell$ red
shell$ ls / somenonexistingfile | formatOutput

The above command will print in both stderr and stdout and you'll see that the lines are coloured differently.

Hope this helps


UPDATE:

To make this reusable, I've put it all in a small script:

$ cat bin/run 
#!/bin/bash
echo -en "\033[31m"  ## red
eval $* | while read line; do
    echo -en "\033[36m"  ## blue
    echo $line
    echo -en "\033[31m"  ## red
done
echo -en "\033[0m"  ## reset color

Now you can use this with any command:

$ run yourCommand

I color stderr red by linking the file descriptor to a custom function that adds color to everything that goes through it. Add to following to your .bashrc:

export COLOR_RED="$(tput setaf 1)"
export COLOR_RESET="$(tput sgr0)"

exec 9>&2
exec 8> >(
    perl -e '$|=1; while(sysread STDIN,$a,9999) {print 
"$ENV{COLOR_RED}$a$ENV{COLOR_RESET}"}'
)
function undirect(){ exec 2>&9; }
function redirect(){ exec 2>&8; } 
trap "redirect;" DEBUG
PROMPT_COMMAND='undirect;'

So what is happening? The debug trap is executed just before and immediately after executing a command. stderr is thus redirected before a command is executed to enable red output. PROMPT_COMMAND is evaluated before the prompt is shown and with this I restore stderr to its normal state. This is necessary because PS1 and PS2 (your prompt) are printed over stderr and I do not want a red prompt. voila, red output over stderr!