59

I want to differentiate the STDOUT and STDERR messages in my terminal. If a script or command is printing a message in terminal I want to differentiate by colors; is it possible?

(E.g. stderr font color is red, and stdout font color is blue.)

Example (using bold):

$date
Wed Jul 27 12:36:50 IST 2011

$datee
bash: datee: command not found

$alias ls
alias ls='ls --color=auto -F'

$aliass ls
bash: aliass: command not found

Community
  • 1
  • 1
ungalnanban
  • 9,539
  • 10
  • 44
  • 55

8 Answers8

78

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.
killdash9
  • 2,314
  • 2
  • 23
  • 17
  • 4
    russ I wish I could give you a huge bounty for this. +1 – Heath Hunnicutt Oct 11 '13 at 00:30
  • 3
    +1 I must admit, your answer is better than mine (but I cannot transfer its acceptance). And your legend is something I would put on a T-shirt. :) – Costi Ciudatu Oct 11 '13 at 21:33
  • 3
    @killdash9 - Thanks for sharing the script. I have found that it consistently swaps the order of some stdout and stderr when they are interleaved. Is there easy fix for this? I will post one here if I figure one out. – Davorak Dec 28 '13 at 19:21
  • @Davorak - Short answer is no. Let me know if you solve this problem. – killdash9 Dec 29 '13 at 04:46
  • I believe [`$(tput setaf 1)`/`$(tput sgr0)`](https://github.com/l0b0/tilde/blob/ebb5d0d7c7ce114227897383eba75eaad8db941e/.bashrc#L53-L57) is more portable than `\e[31m`/`\e[m`. – l0b0 May 12 '14 at 11:54
  • gosh, dungeons of geekyness behold –  Jun 20 '16 at 00:58
  • The problem with this is that `$!` becomes `color` and not the thing you colorize. – Mike Gleason jr Couturier Mar 22 '19 at 09:18
  • It would be nice if you also add example on how to define this function in zsh too. Thanks. – abyss.7 Aug 19 '20 at 21:59
  • @killdash9 If a script that prompts is running when that color command re-organized the streams, will the script still be able to prompt the user? On my machine I was trying to run a big make-based build and it prompts when the build is done for the very first time. The prompt does not go through to the user (the script keeps going) when color is used. It prompts fine otherwise. – PRouleau May 06 '22 at 22:02
  • Do you have a similar solution for some of us less fortunate who are using tcsh? – Anuraag Tummanapally Dec 22 '22 at 08:23
32

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
Costi Ciudatu
  • 37,042
  • 7
  • 56
  • 92
  • while using pipe , if the second command is wrong means it's printing in blue color only not in red. example run ps | grepp run – ungalnanban Jul 27 '11 at 12:50
  • You need to invoke each command as `run `. In your example, `grepp` is not invoked through `run`. Try this: `run ps | run grepp run`. – Costi Ciudatu Jul 27 '11 at 12:55
  • 1
    @ungalnanban: If you really want to execute `ps | grepp run` through the `run` script, you simply need to put that in quotes: `run "ps | grepp run"`. – Costi Ciudatu Jul 27 '11 at 13:36
  • Notice the color mismatch on this example `run "while true; do (echo a; echo b 1>&2) done"`. See also the comment below the C solution. – Stéphane Gimenez Jul 27 '11 at 15:08
  • @Stéphane Gimenez: Yep, you're right. I did say it's just a hack. Also, another drawback that I spotted is that the error messages tend to be displayed first, compared to running the same command without the `run` script. However, you have my upvote for your solution. – Costi Ciudatu Jul 27 '11 at 15:16
  • @Stéphane Gimenez: But it looks like in your script, if you change `1>&2` with `> /dev/stderr`, it starts to behave as expected. Maybe you have an idea why this happens ? `run "while true; do (echo a; echo b > /dev/stderr) done"` – Costi Ciudatu Jul 27 '11 at 15:27
  • On my system this command gives less mismatches, but there are still some left. The only difference I can see is the additional path lookup for "/dev/stderr", which probably leads to one more system call, and it can make some difference in process scheduling (as strange as it sounds). – Stéphane Gimenez Jul 27 '11 at 15:46
  • Instead of hard-coding the escape sequences for the colors, use the `tput` command, e.g., `tput setaf 1` for red. Use `tput sgr0` to reset the attributes afterwards. `tput` reads the terminfo entry for your current terminal (the value of `$TERM`) to get the appropriate control codes and won't send anything the current terminal doesn't understand. – Chris Page Sep 13 '11 at 20:10
12

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!

gospes
  • 3,819
  • 1
  • 28
  • 31
  • Thanks for posting this. I found it with http://serverfault.com/questions/59262/bash-print-stderr-in-red-color . – phyatt Oct 20 '15 at 21:45
  • about the problem with non-newline-terminated lines, unfortunately I dont see why this happens either. I opened a new question https://unix.stackexchange.com/questions/367636/redirect-of-stdout-ignores – phil294 May 27 '17 at 17:49
  • @Blauhirn I just read that thread, having a hard time following how those answers can be incorporated into your above function. Would you mind updating this answer if you were able to fix the newline issue? – Luke Davis Sep 26 '17 at 05:53
  • 2
    @LukeDavis here you go, due to the answers in the linked question, I came up with two possible solutions of which the second one is easier to read: https://pastebin.com/QQQFTP4X and https://pastebin.com/n1n9PcDD. I have not thoroughly tested either of them. See sickill's answer in this thread. stderrred is probably the way to go. – phil294 Sep 26 '17 at 10:43
  • @Blauhirn Thanks for creating the functions! I have updated my answer. – gospes Sep 27 '17 at 13:00
  • Thanks. And yeah the `stderred` tool is probably best, but I often work on servers for which I have no `sudo` permissions, so this approach is really handy (if imperfect). – Luke Davis Sep 27 '17 at 17:13
  • -1 Not a viable solutions. (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 00:45
9

You should check out stderred: https://github.com/sickill/stderred

ku1ik
  • 1,848
  • 22
  • 20
  • http://mywiki.wooledge.org/BashFAQ/037 suggests using tput rather than explicitly writing escape sequences... Also I can't get this working on OS X Mavericks, what could be going wrong? It is only two lines... Also: Great work! I can't wait to get this working – P i Jan 26 '14 at 04:16
  • echo -e isn't necessary: http://stackoverflow.com/questions/21367236/bash-different-colour-for-text-entered-by-user – P i Jan 28 '14 at 15:11
  • For security conscious - `stderred` is not in Debian/Ubuntu standard repos so is not a solution for me. – afora377 May 08 '22 at 01:01
4

Yes it's not possible natively. You'll have to hack the tty management (in the kernel).

I somehow finished some little C wrapper before I saw the other answers :-) Might be buggy, and values are hardcoded, don't use this except for testing.

#include "unistd.h"
#include "stdio.h"
#include <sys/select.h>

int main(int argc, char **argv)
{

char buf[1024];
int pout[2], perr[2];
pipe(pout); pipe(perr);

if (fork()!=0)
{
    close(1); close(2);
    dup2(pout[1],1); dup2(perr[1],2);
    close(pout[1]); close(perr[1]);
    execvp(argv[1], argv+1);
    fprintf(stderr,"exec failed\n");
    return 0;
}

close(pout[1]); close(perr[1]);

while (1)
{
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(pout[0], &fds);
    FD_SET(perr[0], &fds);
    int max = pout[0] > perr[0] ? pout[0] : perr[0];
    int v = select(max+1, &fds, NULL, NULL, NULL);
    if (FD_ISSET(pout[0], &fds))
    {
        int r;
        r = read(pout[0], buf, 1024);
        if (!r) {close(pout[0]); continue;}
        write(1, "\033[33m", 5);
        write(1, buf, r);
        write(1, "\033[0m", 4);
    }
    if (FD_ISSET(perr[0], &fds))
    {
        int r;
        r = read(perr[0], buf, 1024);
        if (!r) {close(perr[0]); continue;}
        write(2, "\033[31m", 5);
        write(2, buf, r);
        write(2, "\033[0m", 4);
    }

    if (v <= 0) break;
}

return 0;
}

Edit: Compared to the shell solution, this one will preserve the order of lines/characters more often. (It's not possible to be as accurate as direct tty reading.) Hitting ^C won't show an ugly error message, and it behaves correctly on this example:

./c_color_script sh -c "while true; do (echo -n a; echo -n b 1>&2) done"
3

I'm surprised that nobody has actually figured out how to color stdio streams. This will color stderr red for the entire (sub)shell:

exec 3>&2
exec 2> >(sed -u 's/^\(.*\)$/'$'\e''[31m\1'$'\e''[m/' >&3)

In this case, &3 will hold the original stderr stream.

You should not be passing any commands to exec, only the redirects. This special case causes exec to replace the current (sub)shell's stdio streams with those that it receives.

There are a few caveats:

  • Since sed will be running persistently in a parallel subshell, any direct output immediately following a write to the colored stdio will probably beat sed to the tty.
  • This method uses a FIFO file descriptor; FIFO nodes only deal in lines. If you don't write a linefeed to the stream, your output will be buffered until a newline is encountered. This is not buffering on sed's part: it's how these file types function.

The most troublesome of the caveats is the first, but a race condition can be more or less avoided by applying similar processing to all outputs, even if you use the default color.

You can perform similar processing for single commands by piping to the same sed command with the normal pipe operator (|). Piped chains are executed synchronously, so no race condition will occur, though the last command in a pipe chain receives its own subshell by default.

Zenexer
  • 18,788
  • 9
  • 71
  • 77
  • 3
    I really like the way that this doesn't require calling a special command, it just forces all output into the right color. Unfortunately, the buffering issue prevents me from even being able to see what I'm typing at my bash prompt (it doesn't appear until after I hit enter). Is there any way to work around that? – robru Oct 31 '13 at 21:09
  • @Robru You might be able to do it with a program that doesn't read line-by-line, but that assumes each character sends an EOD signal, and I'm not sure that it does. You could make an input script that does that if you're feeling ambitious, but you'll lose readline support. – Zenexer Nov 13 '13 at 06:08
  • At least on my system, the line buffering *is* sed's fault. Try `exec 2> >(cat >&3)`, it should be unbuffered. The `stdbuf` command could play a role here too. – Score_Under Sep 03 '14 at 01:19
  • 1
    Does not work - I cannot see what I'm typing any longer. @Score_Under solution did not work either. Debian 10. – afora377 May 08 '22 at 00:58
1

Expanding on the answer @gospes gave, I added the functionality to print out partial lines without waiting for a newline, and some comments. Allows for better output from wget or typing in a interactive shell.

exec 9>&2
exec 8> >(
    while [ "$r" != "1" ]; do
        # read input, no field separators or backslash escaping, 1/20th second timeout
        IFS='' read -rt 0.05 line
        r=$?
        # if we have input, print the color change control char and what input we have
        if ! [ "${#line}" = "0" ]; then
            echo -ne "\e[1;33m${line}"
        fi
        # end of line detected, print default color control char and newline
        if [ "$r" = "0" ] ; then
            echo -e "\e[0m"
        fi
        # slow infinite loops on unexpected returns - shouldn't happen
        if ! [ "$r" = "0" ] && ! [ "$r" = "142" ]; then
            sleep 0.05
        fi
    done
)
function undirect(){ exec 2>&9; }
function redirect(){ exec 2>&8; }
trap "redirect;" DEBUG
PROMPT_COMMAND='undirect;'

I used bold yellow (1;33) but you can replace it with whatever, red for example (31) or bold red (1;33), and I arbitrarily chose 0.05 seconds for re-checking for end-of-lines and pausing on unexpected return codes (never found any); it could probably be lowered, or possibly removed from the read command.

RyanArr
  • 73
  • 6
0

You can make use of grep for this. Note that this assumes that grep is configured to have coloured output (this is the default on many systems).

$ aliass ls 2> >(GREP_COLORS='ms=01;31' grep .) 1> >(GREP_COLORS='ms=01;32' grep .)
aliass: command not found

This is a little long winded, if you are simply wanting to distinguish stderr fromstdout you can simply do this:

$ (echo "this is stdout"; echo "this is stderr" >&2) | grep .
this is stderr
this is stdout

This will result in stdout being highlighted with the default grep colour and stderr being white.

This might be the opposite of what you want if your default grep colour is red. If so you can explicitly set the grep colour to green:

$ GREP_COLORS='ms=01;32'
$ (echo "this is stdout"; echo "this is stderr" >&2) | grep .

If you explicitly want to get red output for stderr:

$ GREP_COLORS='ms=01;31'
$ (echo "this is stdout"; echo "this is stderr" >&2) 2> >(grep .)

Note that this solution will not preserve the output order.

htaccess
  • 2,800
  • 26
  • 31