Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proxy stdout/stderr keeping order in bash

I am trying to add some information in front of every line output from a file, by:

  • catching both stdout and stderr
  • prepending the information
  • outputting to the original handle

Here is my test script:

#!/bin/bash
#
# Test proxying stdout and stderr
#

function proxy-stdouterr() {
    local name="$1"
    local handle=$2
    while IFS='' read -r line
    do
        echo -e "[ ${name}: ${line} ]" >&${handle}
    done
}

# Output some messages and attempt to parse them
(
    echo "1: Normal message"
    echo "2: Error" >&2
    echo "3: Normal message"
    echo "4: Error" >&2
) 2> >(proxy-stdouterr "stderr" 2) > >(proxy-stdouterr "stdout" 1)

This works fairly well but doesn't preserve the order in the terminal (Ubuntu 12.04).

When not proxying outputs:

1: Normal message
2: Error
3: Normal message
4: Error

However when proxying the order isn't kept. Worse this is not deterministic, most of the time it is:

[ stderr: 2: Error ]
[ stderr: 4: Error ]
[ stdout: 1: Normal message ]
[ stdout: 3: Normal message ]

But occasionally:

[ stderr: 2: Error ]
[ stdout: 1: Normal message ]
[ stderr: 4: Error ]
[ stdout: 3: Normal message ]

How can I fix this?

Thanks

like image 959
Mark C Avatar asked Jul 24 '15 10:07

Mark C


1 Answers

As others noted, this is a synchronization problem with no obvious answer without knowing more about which parts you specifically control and want to change.

There are IMHO two main surfaces of attack:

  • stream buffering. Most C-derived programs do not buffer stderr, buffer stdin and stdout by line if they're a terminal and buffer them fully otherwise. This explains why stderr tends to always come up first in your example. A cheap cop-out is to use /usr/bin/echo instead of builtin echo, so you'll get a flushed stream at the end of every line (and a lot of delay for process spawning). It solves your example 100% for what I've tried, so it could be enough for you too. If you've got control on the command that generates the lines, ensure both stdout and stderr are line-buffered.

  • process scheduling. You've got two processes competing to read from both streams. Should there be data flowing on both at the time, they're in a race against each other. You can mitigate that by having a single process do that, but I'm not sure it's possible to keep it efficient in native bash (no programmable POSIX select available to the end-user)

In any case, if there's queueing anywhere in the picture, you're screwed as far as keeping the order goes.

like image 115
JB. Avatar answered Sep 30 '22 01:09

JB.