Every process is initialized with 3 standard file descriptors:

  • standard input
  • standard output
  • standard error

Standard input is read-only, while standard output and standard error are write-only.

In ruby we have both globals and constants to reference these streams:

puts STDIN.object_id  == $stdin.object_id  # true
puts STDOUT.object_id == $stdout.object_id # true
puts STDERR.object_id == $stderr.object_id # true

The constants always point to the default streams, while globals can be changed to point to another I/O stream within the script.

Each file descriptor is represented by a number:

puts STDIN.fileno  # 0
puts STDOUT.fileno # 1
puts STDERR.fileno # 2

These numbers are what we use on command line when we redirect streams.

To demonstrate streams redirection, let’s create few small ruby scripts (in the real world these would be any command line tools you use on daily basis). Create in, out, err and outerr files with following contents and make them executable with chmod +x:

#!/usr/bin/env ruby

p "Standard input read: #{$stdin.read}"
#!/usr/bin/env ruby

$stdout.puts 'out'
#!/usr/bin/env ruby

$stderr.puts 'err'
#!/usr/bin/env ruby

$stdout.puts 'out'
$stderr.puts 'err'

By default, both standard output and standard error are printed on the screen. Run:

./outerr

# out
# err

Stream redirection

Redirect standard output to a file:

./out > file.txt
./out 1> file.txt # same thing

# cat file.txt 
# out

Redirect standard error to a file:

./err 2> file.txt

# cat file.txt 
# err

Redirect both standard output and standard error to a file:

./outerr &> file.txt

# cat file.txt 
# err
# out

Redirect standard error to standard output which is then redirected to a file:

./outerr > file.txt 2>&1

# cat file.txt 
# err
# out

Redirect standard output to standard error and then redirected to a file (nothing redirected):

./outerr > file.txt 1>&2

# cat file.txt
# file.txt is empty

Same applies for appending >>.

Pipes and stream redirection

Same rules apply for pipes | as well, here are few examples to demonstrate that.

By default, only standard output is being passed over pipe:

./outerr | ./in

# err
# "Standard input read: out\n"

Redirect standard error to standard output:

./outerr 2>&1 | ./in

# "Standard input read: err\nout\n"

Redirect standard output to standard error:

./outerr 1>&2 | ./in

# "Standard input read: "
# out
# err