164

Is there a way to make bash display stderr messages in red color?

kolypto
  • 11,058
  • 12
  • 54
  • 66
  • 4
    I guess bash will never colorize its output: some program may want to parse something, and colorizing will spoil data with escaped sequences. A GUI app should handle colors, i guess. – kolypto Aug 26 '09 at 22:20
  • Combining Balázs Pozsár and killdash9 answer gives the crisp: `function color { "$@" 2> >(sed $'s,.*,\e[31m&\e[m,') }` Works for bash and zsh. Can't add this as an answer b/c reputation. – Heinrich Hartmann Sep 14 '18 at 19:38
  • 2
    I am waiting for an answer that modifies bash to do this. The solutions below all *actually* modify stderr and possibly even reorder it w.r.t. stdout which breaks things when the exact byte sequence of stderr must be preserved e.g. when piping. – masterxilo Sep 21 '18 at 09:06
  • 1
    The downside to these solutions is that they work line-by-line, i.e. they buffer the input until a NL is encountered. While that might be okay in most cases, it disables e.g. various progress bars which rely on CR and flushing of output. – Czechnology Dec 01 '20 at 10:37

11 Answers11

136

Method 1: Use process substitution directly:

command 2> >(sed $'s,.*,\e[31m&\e[m,'>&2)

Method 2: Create a function in bash or zsh :

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

Use it like this:

$ color command

Both methods will show the command's stderr in red.

Keep reading for an explanation of how it works. There are some interesting features demonstrated by these commands. The first 3 bullet points only apply to Method 2. The rest apply to both methods.

  • 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> >(...) — The >(...) syntax is called process substitution. Preceded by 2> , it connects the stderr of the main command to the stdin of the sed process inside the parentheses.
  • 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.
Alex Bozhenko
  • 93
  • 1
  • 4
killdash9
  • 1,461
  • 1
  • 9
  • 6
  • 5
    Great answer and even better explanation – Daniel Serodio Oct 22 '14 at 19:56
  • Why do you need to do all the additional redirection? seems like overkill – qodeninja Apr 02 '15 at 15:16
  • @qodeninja The explanation gives the purpose for the redirection. If you can find a simpler way to do it, I'd love to see it! – killdash9 Apr 02 '15 at 16:44
  • @killdash9 Process substitution would be cleaner. – augurar May 08 '16 at 04:55
  • @augurar Can you give an example? – killdash9 May 09 '16 at 00:18
  • @killdash9 Balázs Pozsár's [answer](http://serverfault.com/a/59279/181172) uses this. You can use your `sed` command in place of the `while` loop in his answer. – augurar May 09 '16 at 18:14
  • neat. your answer inspired me to make `mute_err()(set -o pipefail;"$@" 2>/dev/null)` – unsynchronized May 21 '16 at 05:23
  • @augurar I incorporated your suggestion into my answer. Thanks! – killdash9 May 21 '16 at 23:06
  • 1
    Is there a way to make it work in `zsh`? – Eyal Levin Aug 25 '16 at 09:14
  • I wish I could vote twice here. Very _user-friendly_ answer! – 1111161171159459134 Jun 03 '17 at 04:53
  • Unfortunately it does not work in zsh, I get a `parse error near '>&'`. – Czechnology Feb 27 '18 at 08:40
  • 2
    ZSH doesn't recognize the shorthand redirection forms. It just needs two more 1's, i.e. : ```zsh: color()(set -o pipefail;"$@" 2>&1 1>&3|sed $'s,.*,\e[31m&\e[m,'1>&2)3>&1``` – Rekin Aug 02 '18 at 10:12
  • Awesome clever use of redirection here! – laconbass Mar 12 '19 at 22:44
  • Aww fooey, I saw the 2>&1>&3 as shorthand for 2>&1 1>&3 and got excited, thinking I could write 3>&2>&1>&3- as shorthand for 3>&2 2>&1 1>&3- to swap stdout with stderr. But it doesn't work :-( (As you explained.) – Don Hatch Dec 25 '19 at 05:20
  • 1
    The first solution has a bit of a problem in that the pipeline doesn't wait for the output to complete before exiting, so sometimes the colored output will happen after the next prompt, or, if the calling shell exits at that point, it might not happen at all. To demonstrate that consistently, put a `sleep 1;` before the sed. – Don Hatch Dec 25 '19 at 05:24
  • How about adding a `-`, so that fd 3 isn't left open during command? That is, `2>&1>&3-` – Don Hatch Dec 25 '19 at 05:52
  • @DonHatch, when you talk about adding a `-`, are you refering to the first or second method? – killdash9 Jan 13 '20 at 17:23
  • I'm referring to Method 2. So the solution would be: `color()(set -o pipefail;"$@" 2>&1>&3-|sed $'s,.*,\e[31m&\e[m,'>&2)3>&1` To demonstrate (if on linux): `color ls -l /proc/self/fd`. If you add the '-', it shows one less file descriptor open. – Don Hatch Jan 15 '20 at 07:58
  • That's interesting Don, and is a pretty subtle point. Since i believe the extra file handle is benign and gets closed when the process exits anyway, I think I'll leave the answer as is. Thanks for teaching me something new. – killdash9 Jan 17 '20 at 18:56
  • 2
    This won't preserve order of execution. – Jonah Apr 11 '20 at 13:55
  • @Jonah, what do you mean? Can you provide an example? – killdash9 Apr 13 '20 at 18:51
  • see https://unix.stackexchange.com/questions/579298/merge-stdout-and-stderr-and-create-copies-of-each-separately?noredirect=1#comment1078323_579298 – Jonah Apr 13 '20 at 19:27
  • It sounds like by "preserve the order of execution" you mean that the order in which a process writes data to stdout and stderr is the same as the order in which the data appears on the screen. Because stdout and stderr are independent streams, each with internal buffering, this guarantee is not made in general by any process, even when it's not fed through a coloring filter. The coloring filter potentially exacerbates the ordering issue, but I haven't done any testing to determine that. – killdash9 Apr 20 '20 at 17:22
  • It does exacerbate the issue noticeably and doesn't take much to reproduce. Create a shell script containing `echo "stdout"; echo "stderr" 1>&2; echo "stdout"` and compare the results. As you noted, it will be hard to avoid introducing latency unless using a libc wrapper like `stderred`, or using a shell/shell plugin that implements the feature in a way that the color escapes are prefixed to STDERR output without invoking a subprocess or function to render the escapes. – Andrew B May 11 '20 at 15:26
  • 1
    Building off of @killdash9's answer, this solution appears to work well for **both bash and zsh**. As others have noted, a caveat is that ordering between stdout and stderr is not guaranteed to be preserved. `color()(set -o pipefail; "$@" 2> >(sed $'s,.*,\e[31m&\e[m,' >&2))` – Jonathan Wheeler Jan 12 '21 at 23:00
  • 1
    @JonathanWheeler, thank you for the great answer. It's simpler than the one I had, and has the benefit of working for both bas and zsh. I've updated my answer to use this. Well done! – killdash9 Jan 26 '21 at 02:10
  • Is there a way to make the color function be called automatically before every command I type ? – bN_ Jul 29 '21 at 16:21
  • With the current version of method 2 ( ... `color()(set -o pipefail;"$@" 2> >(sed $'s,.*,\e[31m&\e[m,'>&2))` ... ) the concurrency issue is back again, i.e. red output may print AFTER the next prompt was already printed. I haven't had this issue with the current solution from https://stackoverflow.com/a/16178979/923560 : `color()(set -o pipefail;"$@" 2>&1>&3|sed $'s,.*,\e[31m&\e[m,'>&2)3>&1` – Abdull Mar 16 '23 at 10:54
  • Also, with both aforementioned versions, I noticed that they fail coloring stderr if the original stderr data already contains ANSI color escape sequences, e.g. when running `color apt update` as a non-root user: because of missing permissions, apt update sends to stderr some colored output, which wins over `color` function's coloring. – Abdull Mar 16 '23 at 11:56
123
command 2> >(while read line; do echo -e "\e[01;31m$line\e[0m" >&2; done)
Balázs Pozsár
  • 2,133
  • 1
  • 14
  • 16
  • 11
    Great! But i wonder if there's a way to make it permanent :) – kolypto Aug 26 '09 at 21:47
  • #Tync: You would replace 'command' with 'exec', but that will break the prompt, and may also break other commands that use stderr interactively (rm -i, for example). – Juliano Aug 26 '09 at 22:10
  • 10
    Great tip! Suggestion: By adding `>&2` right before `; done)`, the output intended for stderr actually is written to stderr. That's helpful if you want to capture the normal output of the program. – henko Oct 31 '12 at 08:12
  • 1
    I don't have a *nix box handy, so I can't test it. I wonder if the stdout and stderr outputs from 'command' will still be interleaved properly? I'm not sure if output ordering is even guaranteed. +1 anyway. – Les Oct 31 '12 at 12:22
  • 1
    output ordering is never guaranteed between stderr and stdout because they are usually buffered. also, I cannot see why you would ever need to depend on that. (and if you do, you should change your program instead) – Balázs Pozsár Oct 31 '12 at 16:07
  • 8
    The following uses `tput`, and is slightly more readable in my opinion: `command 2> >(while read line; do echo -e "$(tput setaf 1)$line$(tput sgr0)" >&2; done)` – Stefan Lasiewski Feb 14 '13 at 21:59
  • 3
    I think executing 2 tput processes for each output line is not elegant at all. Maybe if you would store the output of the tput commands in a variable and use those for each echo. But then again, readability is not really better. – Balázs Pozsár Oct 07 '14 at 12:09
  • 2
    This solution does not preserve whitespace but I like it for its brevity. `IFS= read -r line` should help but doesn't. Not sure why. – Max Murphy Jul 01 '16 at 11:05
  • 1
    This doesn't preserve the order of execution. – Jonah Apr 11 '20 at 13:54
33

You can also check out stderred: https://github.com/sickill/stderred

ku1ik
  • 431
  • 4
  • 3
  • Wow, this utility is great, the only thing that it would need is to have an apt repository that installs it for all users, with one line, not having to do more work to enable it. – sorin Apr 03 '12 at 14:17
  • Seemed to work well when I tested it with a build script in a separate terminal, but I'm hesitant to use it globally (in `.bashrc`). Thanks though! – Joel Purra Aug 24 '12 at 15:18
  • 2
    In OS X El Capitan, the way this works (DYLD_INSERT_LIBRARIES) is "broken" in system binaries because they are protected by SIP. So it might be better to use the bash options given in other answers. – hmijail Apr 22 '16 at 21:41
  • 1
    @hmijail for MacOS please follow https://github.com/sickill/stderred/issues/60 so we can find a workaround, a partial one already exists but is a little bit buggy. – sorin Jul 16 '18 at 07:57
  • 2
    This is really ingenious. And it's the only answer I've seen that doesn't scramble the order of stdout and stderr (all shell-based solutions do, as far as I can see). – Don Hatch Dec 25 '19 at 05:29
21

The bash way of making stderr permanently red is using 'exec' to redirect streams. Add the following to your bashrc:

exec 9>&2
exec 8> >(
    while IFS='' read -r line || [ -n "$line" ]; do
       echo -e "\033[31m${line}\033[0m"
    done
)
function undirect(){ exec 2>&9; }
function redirect(){ exec 2>&8; }
trap "redirect;" DEBUG
PROMPT_COMMAND='undirect;'

I have posted on this previously: How to set font color for STDOUT and STDERR

gospes
  • 319
  • 2
  • 2
  • related: https://unix.stackexchange.com/questions/367636/redirect-of-stdout-ignores-lines-without-newline?noredirect=1#367654 – phil294 May 29 '17 at 03:59
  • 1
    This is the best answer by far; easy to implement without installation/requiring sudo privilege, and can be generalized to all commands. – Luke Davis Sep 26 '17 at 02:25
  • 1
    Unfortunately this doesn't play well with command chaining (command && nextCommand || errorHandlerCommand). The error output goes after errorHandlerCommand output. – carlin.scott Jun 18 '18 at 23:30
  • 1
    Similarly, if I `source ~/.bashrc` twice with this, my terminal basically locks up. – Dolph Sep 11 '18 at 20:11
  • @Dolf: In my bashrc I easily guard against this with a surrounding if statement to prevent this code from reloading. Otherwise, the problem is the redirection 'exec 9>&2' after redirection has already taken place. Perhaps change it to a constant if you know where >2 is pointing at originally. – gospes Oct 01 '18 at 08:48
  • This does not work on Cygwin – Nathan Aug 26 '19 at 17:22
  • Not a viable solution for me: (i) I cannot $sudo su in terminal any longer, (ii) it colours red non stderr streams e.g. $read "Press Enter", (iii) it disrupts the order of stderr and stdout mingling up messages out of order. – afora377 May 08 '22 at 01:06
14

http://sourceforge.net/projects/hilite/

quaie
  • 1,122
  • 6
  • 14
7

I've made a wrapper script that implements Balázs Pozsár's answer in pure bash. Save it in your $PATH and prefix commands to colorize their output.


    #!/bin/bash

    if [ $1 == "--help" ] ; then
        echo "Executes a command and colorizes all errors occured"
        echo "Example: `basename ${0}` wget ..."
        echo "(c) o_O Tync, ICQ# 1227-700, Enjoy!"
        exit 0
        fi

    # Temp file to catch all errors
    TMP_ERRS=$(mktemp)

    # Execute command
    "$@" 2> >(while read line; do echo -e "\e[01;31m$line\e[0m" | tee --append $TMP_ERRS; done)
    EXIT_CODE=$?

    # Display all errors again
    if [ -s "$TMP_ERRS" ] ; then
        echo -e "\n\n\n\e[01;31m === ERRORS === \e[0m"
        cat $TMP_ERRS
        fi
    rm -f $TMP_ERRS

    # Finish
    exit $EXIT_CODE

kolypto
  • 11,058
  • 12
  • 54
  • 66
3

You can use a function like this


 #!/bin/sh

color() {
      printf '\033[%sm%s\033[m\n' "$@"
      # usage color "31;5" "string"
      # 0 default
      # 5 blink, 1 strong, 4 underlined
      # fg: 31 red,  32 green, 33 yellow, 34 blue, 35 purple, 36 cyan, 37 white
      # bg: 40 black, 41 red, 44 blue, 45 purple
      }
string="Hello world!"
color '31;1' "$string" >&2

I append >&2 to print to stderr

Ali Mezgani
  • 3,850
  • 2
  • 24
  • 36
  • 4
    Not addressing the problem. You haven't provided a way of separating stderr from stdout, which is what the O.P. is interested in. – Jeremy Visser Aug 27 '09 at 01:54
1

I have a slightly modified version of O_o Tync's script. I needed to make these mods for OS X Lion and it's not perfect because the script sometimes completes before the wrapped command does. I've added a sleep but I'm sure there's a better way.

#!/bin/bash

   if [ $1 == "--help" ] ; then
       echo "Executes a command and colorizes all errors occured"
       echo "Example: `basename ${0}` wget ..."
       echo "(c) o_O Tync, ICQ# 1227-700, Enjoy!"
       exit 0
       fi

   # Temp file to catch all errors
   TMP_ERRS=`mktemp /tmp/temperr.XXXXXX` || exit 1

   # Execute command
   "$@" 2> >(while read line; do echo -e "$(tput setaf 1)$line\n" | tee -a $TMP_ERRS; done)
   EXIT_CODE=$?

   sleep 1
   # Display all errors again
   if [ -s "$TMP_ERRS" ] ; then
       echo -e "\n\n\n$(tput setaf 1) === ERRORS === "
       cat $TMP_ERRS
   else
       echo "No errors collected in $TMP_ERRS"
   fi
   rm -f $TMP_ERRS

   # Finish
   exit $EXIT_CODE
Cliff
  • 111
  • 2
1

This solution worked for me: https://superuser.com/questions/28869/immediately-tell-which-output-was-sent-to-stderr

I've put this function in my .bashrc or .zshrc:

# Red STDERR
# rse <command string>
function rse()
{
    # We need to wrap each phrase of the command in quotes to preserve arguments that contain whitespace
    # Execute the command, swap STDOUT and STDERR, colour STDOUT, swap back
    ((eval $(for phrase in "$@"; do echo -n "'$phrase' "; done)) 3>&1 1>&2 2>&3 | sed -e "s/^\(.*\)$/$(echo -en \\033)[31;1m\1$(echo -en \\033)[0m/") 3>&1 1>&2 2>&3
}

Then for example:

$ rse cat non_existing_file.txt

will give me a red output.

Eyal Levin
  • 121
  • 3
1

using xargs and printf:

command 2> >(xargs -0 printf "\e[31m%s\e[m" >&2)
0

a version using fifos

mkfifo errs
stdbuf -o0 -e0 -i0 grep . foo | while read line; do echo -e "\e[01;31m$line  \e[0m" >&2; done &
stdbuf -o0 -e0 -i0 sh $script 2>errs
untore
  • 101
  • 1