Home > Mobile >  How do you make a shell function that you can pipe into to make the output red?
How do you make a shell function that you can pipe into to make the output red?

Time:09-23

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 a printf 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 shell read returns false in this case after reading the remaining text, and break out of the while loop without processing the last line not ending with a newline.
    It is dealt with testing if the line 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.

  • Related