Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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 ?

share|improve this question
add comment

3 Answers

up vote 2 down vote accepted

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.

share|improve this answer
    
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? –  louism Dec 14 '12 at 17:14
    
Before, ie. mongod | colorfilter & pid_mongo=$! –  TAFKA '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. –  TAFKA '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. –  louism Dec 14 '12 at 17:50
    
@louism : okay, I added some code and a link to a tarball –  TAFKA 'goldilocks' Dec 16 '12 at 18:50
add comment
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/" &
share|improve this answer
    
+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
add comment

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..

share|improve this answer
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. –  TAFKA 'goldilocks' Dec 16 '12 at 19:13
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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