Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does STDERR output from Ruby come before earlier STDOUT output when redirecting?

Tags:

bash

ruby

In bash, this gives the output in the expected order:

ruby -e "puts 'one'; raise 'two'"
one
-e:1:in `<main>': two (RuntimeError)

But if I redirect STDERR to STDOUT, I get the error before the output, which I do not want:

ruby -e "puts 'one'; raise 'two'" 2>&1 | cat
-e:1:in `<main>': two (RuntimeError)
one

I want to redirect the output to a text file (it behaves the same way as cat above) and get both output and exceptions, but in the same order as when looking at the output in my terminal. Can this be achieved?

like image 959
Henrik N Avatar asked Jan 17 '12 20:01

Henrik N


People also ask

Where does stderr go by default?

By default, stderr is typically connected to the same place as stdout, i.e. the current terminal.

What is stdout in Ruby?

In Ruby, $stdout represents the current standard output and a global variable. It means we can also use it to output data.


2 Answers

It's because STDOUT doesn't always output right away, to force it to output you use IO#flush:

puts "one"
$>.flush

STDERR on the other hand always outputs immediately.

like image 27
robbrit Avatar answered Oct 19 '22 04:10

robbrit


This happens because of line-buffering vs block-buffering. You can control the type of buffering, you can flush them at the point you want their output to be synced, or you can just wait until exit at which point everything gets flushed. Unless you force it one way or the other, buffering depends on whether the output is a tty-type1 file descriptor, so redirection into a pipe changes the mode.

Specifically:

                 true          false 
              ------------- --------------
$stdout.tty?  line-buffered block-buffered
$stderr.tty?  line-buffered line-buffered

You can configure them both the same way with:

$stdout.sync = $stderr.sync = true # or false, of course

My test case:

$stdout.sync = $stderr.sync = true
$stdout.puts 'stdout a'
sleep 2
$stdout.puts 'stdout b'
sleep 2
$stderr.puts 'stderr a'
sleep 2
$stderr.puts 'stderr b'
sleep 2


1. See ttyname(3).
like image 171
DigitalRoss Avatar answered Oct 19 '22 03:10

DigitalRoss