I want something where I can do {some command} | red
and it'll output whatever {some command}
would have outputted, but red. The best I've been able to do is
function red()
{
while read text
do
echo -e '\033[31m'$text'\033[0m'
done
}
But this removes all the indentation.
This seems like something that should be easy but I just can't seem to figure it out. I was trying to do it just for fun but now I just need to know, 'cause there has to be a simple way to do this that I'm missing.
EDIT:
I've also tried this in C like so:
#include <stdio.h>
#include <stdlib.h>
int main()
{
char* buffer = malloc(8);
int size = 8;
int index = 0;
while ((buffer[index ] = getchar()) != EOF)
{
if (index == size)
{
size *= 2;
buffer = realloc(buffer, size);
}
}
buffer[index] = 0;
printf("%s%s%s", "\\033[31m", buffer, "\\033[0m");
}
But the shell doesn't interpret the escape characters, and so doesn't make the output red.
CodePudding user response:
It was only missing a couple things for it to work:
- Firstly you want to neutralize the Internal Field Separator
IFS
environment variable, because it was the cause of your discarded indentation. This variable caused the read operation to eat leading tabs and spaces as field separator. - Next you wan to
read -r
. The-r
flag prevent the interpretation of escaped characters and have a raw read instead. - Finally, you must not use
echo -e
. It is a horrible non-standard way of printing\
escaped characters. Use aprintf
instead. - Additionally, the loop on the
read
will not handle the last line of text if it does not contain an newline character\n
. The shellread
returnsfalse
in this case after reading the remaining text, and break out of thewhile
loop without processing the last line not ending with a newline.
It is dealt with testing if theline
contains a last line of text and print it without a newline for this special last case.
Now here it is all fixed and working as you intended to:
#!/usr/bin/env bash
function red() {
while IFS= read -r line; do
printf '\e[31m%s\e[0m\n' "$line"
done
[ -n "$line" ] && printf '\e[31m%s\e[0m' "$line"
}
red
EDIT:
As @Thsutton commented above, you can use cat
in the red
function to insert the ANSI CSI sequences before and after the whole text only, rather than for each line of text. It does not need a while loop, so will undoubtedly be more performant. Although note that the per-line method might give better results if the text stream itself contains ANSI CSI sequences changing the terminal colour.
red() {
printf '\e[31m'
cat
printf '\e[0m'
}
CodePudding user response:
Writing the escape codes explicitly is IMO cumbersome. I would do something like
red() {
tput setaf 196
cat
tput sgr0
}
The magical number 196 comes from this table.