bash: print stderr in red color

60,833

Solution 1

command 2> >(while read line; do echo -e "\e[01;31m$line\e[0m" >&2; done)

Solution 2

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))

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.

Solution 3

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

Solution 4

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

Solution 5

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

Share:
60,833

Related videos on Youtube

streetlight
Author by

streetlight

Enthusiastic Python/JS back-end developer. Loves exceptional projects and challenging tasks. Architects chaos into well-structured formations. Friendly and business-minded. Loves people. Always smiles :) CV and Contacts

Updated on September 17, 2022

Comments

  • streetlight
    streetlight almost 2 years

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

    • streetlight
      streetlight almost 15 years
      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.
    • Heinrich Hartmann
      Heinrich Hartmann almost 6 years
      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.
    • masterxilo
      masterxilo almost 6 years
      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.
    • Czechnology
      Czechnology over 3 years
      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.
  • streetlight
    streetlight almost 15 years
    Great! But i wonder if there's a way to make it permanent :)
  • Zaid Amir
    Zaid Amir almost 15 years
    This could be made more efficient if "|tee..." was put after "done".
  • Jeremy Visser
    Jeremy Visser almost 15 years
    Not addressing the problem. You haven't provided a way of separating stderr from stdout, which is what the O.P. is interested in.
  • sorin
    sorin about 12 years
    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.
  • Joel Purra
    Joel Purra almost 12 years
    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!
  • henko
    henko over 11 years
    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.
  • Les
    Les over 11 years
    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.
  • Balázs Pozsár
    Balázs Pozsár over 11 years
    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)
  • Stefan Lasiewski
    Stefan Lasiewski over 11 years
    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)
  • muhqu
    muhqu almost 11 years
    +1 best answer! absolutly underrated!
  • Balázs Pozsár
    Balázs Pozsár over 9 years
    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.
  • qodeninja
    qodeninja about 9 years
    Why do you need to do all the additional redirection? seems like overkill
  • killdash9
    killdash9 about 9 years
    @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!
  • augurar
    augurar about 8 years
    @killdash9 Process substitution would be cleaner.
  • killdash9
    killdash9 about 8 years
    @augurar Can you give an example?
  • augurar
    augurar about 8 years
    @killdash9 Balázs Pozsár's answer uses this. You can use your sed command in place of the while loop in his answer.
  • unsynchronized
    unsynchronized about 8 years
    neat. your answer inspired me to make mute_err()(set -o pipefail;"$@" 2>/dev/null)
  • killdash9
    killdash9 about 8 years
    @augurar I incorporated your suggestion into my answer. Thanks!
  • Max Murphy
    Max Murphy almost 8 years
    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.
  • Eyal Levin
    Eyal Levin almost 8 years
    Is there a way to make it work in zsh?
  • phil294
    phil294 about 7 years
  • 1111161171159459134
    1111161171159459134 about 7 years
    I wish I could vote twice here. Very user-friendly answer!
  • Luke Davis
    Luke Davis almost 7 years
    This is the best answer by far; easy to implement without installation/requiring sudo privilege, and can be generalized to all commands.
  • Czechnology
    Czechnology over 6 years
    Unfortunately it does not work in zsh, I get a parse error near '>&'.
  • carlin.scott
    carlin.scott about 6 years
    Unfortunately this doesn't play well with command chaining (command && nextCommand || errorHandlerCommand). The error output goes after errorHandlerCommand output.
  • sorin
    sorin almost 6 years
    @hmijail for MacOS please follow github.com/sickill/stderred/issues/60 so we can find a workaround, a partial one already exists but is a little bit buggy.
  • Rekin
    Rekin almost 6 years
    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
  • Dolph
    Dolph almost 6 years
    Similarly, if I source ~/.bashrc twice with this, my terminal basically locks up.
  • gospes
    gospes over 5 years
    @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.
  • laconbass
    laconbass over 5 years
    Awesome clever use of redirection here!
  • kvaps
    kvaps about 5 years
    You can add set -o pipefail; before (eval for redirect exit code
  • kvaps
    kvaps about 5 years
    also add the " to eval to preserve spaces in the arguments
  • Nathan
    Nathan almost 5 years
    This does not work on Cygwin
  • Don Hatch
    Don Hatch over 4 years
    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
    Don Hatch over 4 years
    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
    Don Hatch over 4 years
    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
    Don Hatch over 4 years
    How about adding a -, so that fd 3 isn't left open during command? That is, 2>&1>&3-
  • killdash9
    killdash9 over 4 years
    @DonHatch, when you talk about adding a -, are you refering to the first or second method?
  • Don Hatch
    Don Hatch over 4 years
    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.
  • killdash9
    killdash9 over 4 years
    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.
  • Jonah
    Jonah about 4 years
    This doesn't preserve the order of execution.
  • Jonah
    Jonah about 4 years
    This won't preserve order of execution.
  • killdash9
    killdash9 about 4 years
    @Jonah, what do you mean? Can you provide an example?
  • Jonah
    Jonah about 4 years
  • killdash9
    killdash9 about 4 years
    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.
  • Andrew B
    Andrew B about 4 years
    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.
  • Jonathan Wheeler
    Jonathan Wheeler over 3 years
    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))
  • killdash9
    killdash9 over 3 years
    @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!
  • bN_
    bN_ almost 3 years
    Is there a way to make the color function be called automatically before every command I type ?
  • Admin
    Admin about 2 years
    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.