2

I am trying to have my stderr be printed red in terminal. The below script redirects the 2 to a custom 8 upon debug trap.

exec 9>&2
exec 8> >(
    while IFS='' read -r line || [ -n "$line" ]; do
       echo -e "${RED}${line}${COLORRESET}"
    done
)
function undirect(){ exec 2>&9; } # reset to original 9 (==2)
function redirect(){ exec 2>&8; } # set to custom 8
trap "redirect;" DEBUG
PROMPT_COMMAND='undirect;'

It comes from here, with a clear explanation.

Seems to work very fine, however non-newline-terminated input doesn't get printed out at all. Quoting the author gospes again:

bash> echo -en "hi\n" 1>&2
    hi       <-- this is red
bash> echo -en "hi" 1>&2
bash> echo -en "hi" 1>&2
bash> echo -en "hi\n" 1>&2
    hihihi   <-- this is red

I cannot figure out why. The non-newline content seems to end up in some kind of buffer. It either doesn't even reach the file descriptor 8, or somehow doesn't want to be printed out right away. Where does it go? redirect gets called properly every time. Also, IFS='' means there is no delimiter, so i don't quite understand why the echoing out in 8 happens line-wise.

A bugfix would be much appreciated, I linked the quoted answer to this question.

This entire solution is, as pointed out by Gilles, not quite perfect. I am having issues with read, stdin, progress bars, can neither su nor source. And frequently major problems like broken pipes and unexpected terminal exits. If anybody got here by my linking, please do consider using https://github.com/sickill/stderred instead, it is much better (no problems yet) (however echo bla >&2 remains non-red and the respective issue is closed)

  • 1
    What you're trying to do cannot be done reliably (unless you do it inside the terminal emulator), so don't waste too much time on it. The best you can do is to make it work in a few more cases. – Gilles 'SO- stop being evil' May 27 '17 at 22:57
5

You did get the partial lines output, as part of the same line at the point where the newline was printed. The parts of the line are buffered within read, that's what it does:

The read utility shall read a single logical line from standard input

For example, this prints <foobar> after one second, not <foo><bar>.

(echo -n foo ; sleep 1 ; echo bar) | (read x ; echo "<$x>")

If you want to catch input in smaller parts than full lines, you'll need to do something else, e.g. with Perl. This would print <foo><bar\n> (with the newline before the last >, since unlike read, Perl doesn't handle the final newline specially. Shouldn't matter with coloring.)

(echo -n foo ; sleep 1 ; echo bar) | 
    perl -e '$|=1; while(sysread STDIN,$a,9999) { print "<$a>"}'

If you have the control codes for colors (RED and COLORRESET) exported in the environment, you can use them from the Perl script as here:

perl -e '$|=1; while(sysread STDIN,$a,9999) {print "$ENV{RED}$a$ENV{COLORRESET}"}'
1

In Bash you can use the -d option to the read builtin, which defines the end of line symbol. man bash states this:

-d delim    The first character of delim is used to terminate the input line, 
            rather than newline.

If it's not defined, read waits for \n to appear to consider a string a line. But when you use the -d option, you can set NUL as the delimiter. Of course you also need to NUL-terminate the input then.

Example:

printf "%s\0" $'x\n' y z | while IFS='' read -r -d $'\0' line
    do
        printf "%s\n" "$line"
    done

Output:

x

y
z

Once again, but now the printf in the while loop doesn't stand with \n.

printf "%s\0" $'x\n' y z | while IFS='' read -r -d $'\0' line
    do
        printf "%s" "$line"
    done

Output:

x
yz(...)

I added the (...) and it means there's no End-Of-Line at the end of the second line. But the text still gets processed and printed.

  • so '$\n' is a typo, should be 'x\n', right? – phil294 May 27 '17 at 22:57
  • what you refer to as printf [sth. with \0-terminated input] is the stderr of any program, really. how would that be \0-terminated? it is what needs to be read in the first place. - I dont quite understand where the printf would go in regards to the question – phil294 May 27 '17 at 22:58
  • 1
    @Blauhirn Sorry, typo. Two of them. I corrected them. ` $'x\n'` or x$'\n' is correct. – Tomasz May 27 '17 at 23:10
  • @Blauhirn Stderr is a separate file descriptor. Terminating with \0 works regardless of the stream, be it stderr or stdout. As long as later the receiver uses \0 as well. printf with \0 works similar to echo -en '\0'. – Tomasz May 27 '17 at 23:15
  • I am sorry, I dont understand what you're saying. Yes, like in your answer, the print can simply be \0-terminated. But nobody's using a print or echo here. The input comes from stderr (redirected using debug trap). This input is not \0-terminated. So your solution will never print out anything. – phil294 Sep 26 '17 at 10:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.