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