In Progress
Unit 1, Lesson 21
In Progress

Suppress Output

Sometimes you just want to say “shhhh!” to program output. Especially when writing automations that manage other processes, you may need a way to suppress all output for a period of time. In this episode you’ll learn a technique for silencing console output that is robust, customizable, and works across platforms.

Video transcript & code

We have some code, part of which generates a bunch of boring output that we don't care about.

puts "Starting up"
puts "BLAH BLAH BLAH YADDA YADDA LOREM IPSUM PEAS AND CARROTS"
puts "Shutting down"

# >> Starting up
# >> BLAH BLAH BLAH YADDA YADDA LOREM IPSUM PEAS AND CARROTS
# >> Shutting down

Not only does it generate unwanted output directly, but it also starts subprocesses which also have verbose output.

puts "Starting up"
puts "BLAH BLAH BLAH YADDA YADDA LOREM IPSUM PEAS AND CARROTS"
system %(ruby -e 'puts "EVEN MORE BLAH BLAH BLAH"')
puts "Shutting down"

# >> Starting up
# >> BLAH BLAH BLAH YADDA YADDA LOREM IPSUM PEAS AND CARROTS
# >> EVEN MORE BLAH BLAH BLAH
# >> Shutting down

Back in Episode #29, we covered a robust method for redirecting and capturing output from both the current process and any subprocesses. But we don't want to capture output here. We just want to make it go away.

We could just redirect all of the output to a file. To do this, we need to open the output file in write mode. Then pass the resulting IO object into the reopen method on the $stdout object.

This suppresses everything after the reopen call.

puts "Starting up"
junk = open("boring.out", "w")
# => #<File:boring.out>
$stdout.reopen(junk)
puts "BLAH BLAH BLAH YADDA YADDA LOREM IPSUM PEAS AND CARROTS"
system %(ruby -e 'puts "EVEN MORE BLAH BLAH BLAH"')
puts "Shutting down"

# >> Starting up

In fact, it gets rid of all output until the program ends. This isn't quite what we want. We just want to silence the uninteresting parts.

In order to reinstate the original standard output stream later on, we need to first save a duplicate or clone of it and then once again call reopen when we want to go back to showing output. Now we can once again see our "shutting down" output.

puts "Starting up"
stdout = $stdout.dup
# => #<IO:<STDOUT>>
junk = open("boring.out", "w")
# => #<File:boring.out>
$stdout.reopen(junk)
puts "BLAH BLAH BLAH YADDA YADDA LOREM IPSUM PEAS AND CARROTS"
system %(ruby -e 'puts "EVEN MORE BLAH BLAH BLAH"')
$stdout.reopen(stdout)
puts "Shutting down"

# >> Starting up
# >> Shutting down

So far we haven't seen anything we didn't already cover in Episode #29. And we haven't actually gotten rid of output, either. We've just hidden it in a file.

Before we go further, let's tighten up this code. Instead of separately creating a new IO object for the output file, we can pass the name of the file directly to reopen.

puts "Starting up"
stdout = $stdout.dup
# => #<IO:<STDOUT>>
$stdout.reopen("boring.out", "w")
puts "BLAH BLAH BLAH YADDA YADDA LOREM IPSUM PEAS AND CARROTS"
system %(ruby -e 'puts "EVEN MORE BLAH BLAH BLAH"')
$stdout.reopen(stdout)
puts "Shutting down"

# >> Starting up
# >> Shutting down

This illustrates that reopen will accept raw filenames as well as IO objects.

If the output we are hiding is truly copious, it might not be enough to just conceal it in a file. If this code is running on a small virtual machine, for instance, we might both degrade performance and risk running out of disk space by writing the output to disk.

The obvious answer is to write to the null device instead of to a real file.

puts "Starting up"
stdout = $stdout.dup
# => #<IO:<STDOUT>>
$stdout.reopen("/dev/null", "w")
puts "BLAH BLAH BLAH YADDA YADDA LOREM IPSUM PEAS AND CARROTS"
system %(ruby -e 'puts "EVEN MORE BLAH BLAH BLAH"')
$stdout.reopen(stdout)
puts "Shutting down"

# >> Starting up
# >> Shutting down

This is fine if we know for sure what kind of platform our code is going to be running on, now and in future. It's less fine if this is public library code. Not all machines have a /dev/null device.

Fortunately, there's an alternative. We can use the File::NULL constant to specify the appropriate null device name for whatever operating system Ruby is running on.

puts "Starting up"
stdout = $stdout.dup
# => #<IO:<STDOUT>>
$stdout.reopen(File::NULL, "w")
puts "BLAH BLAH BLAH YADDA YADDA LOREM IPSUM PEAS AND CARROTS"
system %(ruby -e 'puts "EVEN MORE BLAH BLAH BLAH"')
$stdout.reopen(stdout)
puts "Shutting down"

# >> Starting up
# >> Shutting down

If we find ourselves wanting this kind of output-suppression often, we could even make a method for it that lets us choose whether we want to suppress the standard output stream, the standard error stream, or both.

def suppress_output(out: true, err: false)
  null = open(File::NULL, "w")
  old_out = $stdout.dup
  old_err = $stderr.dup
  $stdout.reopen(null) if out
  $stderr.reopen(null) if err
  yield
ensure
  $stdout.reopen(old_out)
  $stderr.reopen(old_err)
end

puts "Starting up"
suppress_output do
  puts "BLAH BLAH BLAH YADDA YADDA LOREM IPSUM PEAS AND CARROTS"
  system %(ruby -e 'puts "EVEN MORE BLAH BLAH BLAH"')
end
puts "Shutting down"

# >> Starting up
# >> Shutting down

And there you have it: a reliable, cross-platform way to suppress unwanted output in Ruby. Happy hacking!

Responses