7

I have a runscript that starts some processes and sends them to the background

mongod       & pid_mongo=$!
redis-server & pid_redis=$!
# etc.

All these processes then output concurrently to the same standard output. My question: is it possible to color the output of each different forked process, so that - for example - one of them outputs in green and the other one in red ?

user2398029
  • 215
  • 2
  • 5

4 Answers4

5
red=$(tput setaf 1)
green=$(tput setaf 2)
default=$(tput sgr0)
cmd1 2>&1 | sed "s/.*/$red&$default/" &
cmd2 2>&1 | sed "s/.*/$green&$default/" &
  • +1 To test, define e.g. cmd1() { while true; do echo foo; sleep 1; done } and cmd2() { while true; do echo bar; sleep 3; done } – l0b0 Dec 17 '12 at 13:02
2

You could do this by piping through a filter, it is just a matter of adding appropriate ANSI codes before and after each line:

http://en.wikipedia.org/wiki/ANSI_escape_sequences#Colors

I could not find a tool which actually does this after a few minutes googling, which is bit odd considering how easy it would be to write one.

Here's an idea using C:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>

/* std=gnu99 required */

// ANSI reset sequence
#define RESET "\033[0m\n"
// length of RESET
#define RLEN 5
// size for read buffer
#define BUFSZ 16384
// max length of start sequence
#define START_MAX 12

void usage (const char *name) {
    printf("Usage: %s [-1 N -2 N -b -e | -h]\n", name);
    puts("-1 is the foreground color, -2 is the background.\n"
        "'N' is one of the numbers below, corresponding to a color\n"
        "(if your terminal is not using the standard palette, these may be different):\n"
        "\t0 black\n"
        "\t1 red\n"
        "\t2 green\n"
        "\t3 yellow\n"
        "\t4 blue\n"
        "\t5 magenta\n"
        "\t6 cyan\n"
        "\t7 white\n"
        "-b sets the foreground to be brighter/bolder.\n"
        "-e will print to standard error instead of standard out.\n"
        "-h will print this message.\n"
    );
    exit (1);
}


// adds character in place and increments pointer
void appendChar (char **end, char c) {
    *(*end) = c;
    (*end)++;
}


int main (int argc, char *const argv[]) {
// no point in no arguments...
    if (argc < 2) usage(argv[0]);

// process options
    const char options[]="1:2:beh";
    int opt,
        set = 0,
        output = STDOUT_FILENO;
    char line[BUFSZ] = "\033[", // ANSI escape
        *p = &line[2];

    // loop thru options
    while ((opt = getopt(argc, argv, options)) > 0) {
        if (p - line > START_MAX) usage(argv[0]);
        switch (opt) {
            case '?': usage(argv[0]);
            case '1': // foreground color
                if (
                    optarg[1] != '\0'
                    || optarg[0] < '0'
                    || optarg[0] > '7'
                ) usage(argv[0]);
                if (set) appendChar(&p, ';');
                appendChar(&p, '3');
                appendChar(&p, optarg[0]);
                set = 1;
                break;
            case '2': // background color
                if (
                    optarg[1] != '\0'
                    || optarg[0] < '0'
                    || optarg[0] > '7'
                ) usage(argv[0]);
                if (set) appendChar(&p, ';');
                appendChar(&p, '4');
                appendChar(&p, optarg[0]);
                set = 1;
                break;
            case 'b': // set bright/bold
                if (set) appendChar(&p, ';');
                appendChar(&p, '1');
                set = 1;
                break;
            case 'e': // use stderr
                output = STDERR_FILENO;
                break;
            case 'h': usage(argv[0]);
            default: usage(argv[0]);
        }
    }
    // finish 'start' sequence
    appendChar(&p, 'm');

// main loop

    // set non-block on input descriptor
    int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);

    // len of start sequence
    const size_t slen = p - line,
    // max length of data to read
        rmax = BUFSZ - (slen + RLEN);
    // actual amount of data read
    ssize_t r;
    // index of current position in output line
    size_t cur = slen;
    // read buffer
    char buffer[rmax];
    while ((r = read(STDIN_FILENO, buffer, rmax))) {
        if (!r) break;  // EOF
        if (r < 1) {
            if (errno == EAGAIN) continue;
            break;  // done, error
        }
        // loop thru input chunk byte by byte
        // this is all fine for utf-8
        for (int i = 0; i < r; i++) {
            if (buffer[i] == '\n' || cur == rmax) {
            // append reset sequence
                for (int j = 0; j < RLEN; j++) line[j+cur] = RESET[j];
            // write out start sequence + buffer + reset
                write(output, line, cur+RLEN);
                cur = slen;
            } else line[cur++] = buffer[i];
        }
    }
    // write out any buffered data
    if (cur > slen) {
        for (int j = 0; j < RLEN; j++) line[j+cur] = RESET[j];
        write(output, line, cur+RLEN);
    }
    // flush
    fsync(output);

// the end
    return r;
}                                       

I think that is about as efficient as you are going to get. The write() needs to do an entire line with the ANSI sequences all in one go -- testing this with parallel forks led to interleaving if the ANSI sequences and the buffer content were done separately.

That needs to be compiled -std=gnu99 since getopt is not part of the C99 standard but it is part of GNU. I tested this somewhat with parallel forks; that source, a makefile, and the tests are in a tarball here:

http://cognitivedissonance.ca/cogware/utf8_colorize/utf8_colorize.tar.bz2

If the application you use this with logs to standard error, remember to redirect that too:

application 2>&1 | utf8-colorize -1 2 &

The .sh files in the test directory contain some usage examples.

goldilocks
  • 87,661
  • 30
  • 204
  • 262
  • Care to explain a bit further (I'm a real beginner in bash)? Do I need to add in the piping instruction before or after forking? – user2398029 Dec 14 '12 at 17:14
  • Before, ie. mongod | colorfilter & pid_mongo=$! – goldilocks Dec 14 '12 at 17:22
  • I've decided to goof off a bit and code something for this in C, so if you can't find anything else, come back later today or tomorrow and I'll stick a link/some source up for that. There really should be a simple, compiled tool for this. – goldilocks Dec 14 '12 at 17:32
  • Awesome. I'll see what I can do on my side and look forward to comparing that with your take on it. – user2398029 Dec 14 '12 at 17:50
  • @louism : okay, I added some code and a link to a tarball – goldilocks Dec 16 '12 at 18:50
  • You should be using terminfo or termcap to get the control sequences for the terminal. Not all stdout is a VT100! But it makes little difference given that this method is flawed, with plenty of potential for races. What you really need is a multiplexer that reads from all the writers (probably using select()) and colourises each block according to which fd it was read from. – Toby Speight Sep 23 '15 at 16:10
  • @TobySpeight I admit to not having used this much myself and there's certainly much to improve upon -- by writing more code, which I haven't since it isn't that important to me. However (as noted in the answer) this actually doesn't produce any interleaving when tested, presumably because of stdout buffering (put another way, the race condition is no different than it would be without any filter; if your tish is interleaving already, this will not fix it, but it will not create it either). It's a quick hack method, that's all. – goldilocks Sep 23 '15 at 16:40
0

another option you can use with sh built in function: works also on ash (busybox) ;)

RED=`echo -e '\033[0;31m'`
NC=`echo -e '\033[0m'` # No Color

cmdx 2>&1 | sed "s/.*/$RED&$NC/" &

I wrote myself a shell function to easily run programs in the background. This is writen for busybox' ash! Works in bash as well. Just run:

bg_red <whatever cmd you want to run in the bg>

put following into your .bashrc

ESC="\033"                                                                      
# Colors:
COLOR_RED_F="${ESC}[31m"
COLOR_GREEN_F="${ESC}[32m" 
COLOR_RESET="${ESC}[0m" 

bg_red()            
{                                                                               
   [ "$#" -lt "1" ] && echo "bg_red() <cmd to run in bg>" && return 1           
   PIPE_RED=`echo -e $COLOR_RED_F`                                              
   PIPE_NC=`echo -e $COLOR_RESET`                                               
   $@ 2>&1 | sed "s/.*/$PIPE_RED&$PIPE_NC/" &                                   
}                                  

as seen here: http://www.bramschoenmakers.nl/en/node/511.html

stfl
  • 101
0

You are probably better of redirecting the logs to specific output files?

There are many different solutions for colorizing output. The easiest probably would be to use the grc package..

Not Now
  • 2,572
  • 1
    Redirecting to separate files is not the best option if a) you are trying to observe/debug something in real time. b) you want the order of events from multiple processes preserved without having to compare time-stamps in separate files. I would guess the OP is testing a front end server combined with a back-end database server (mongo) and wants to see what happens when. – goldilocks Dec 16 '12 at 19:13