puts
Video transcript & code
It may seem strange to have an episode dedicated to puts
. After all, puts
was probably the first Ruby method you ever learned about. But even prosaic methods can have hidden depth, and that's just the case with puts
.
First of all, let's talk about the name. I've heard puts
pronounced "puts", as in "it puts the lotion in the basket". This is understandable given how the method is spelled, but it's also misleading. puts
is short for "PUT String". It's a name inherited from the C standard library of the same name.
$ man 3 puts
When we write puts
by itself in a Ruby program, we're implicitly invoking the Kernel#puts
method.
puts "Hello, world"
$ ri Kernel#puts = Kernel#puts (from ruby site) ------------------------------------------------------------------------------ puts(obj, ...) -> nil ------------------------------------------------------------------------------ Equivalent to $stdout.puts(obj, ...)
This is just a thin wrapper that forwards to the $stdout
global variable.
$stdout.puts "Hello, world"
As we discussed in episode 29, this makes it easy to redirect output by reassigning the $stdout
variable. For instance, we could make all puts
sends write to a file instead.
$stdout = open("out.log", 'w')
puts "Hello, world"
$stdout.close
File.read("out.log")
# => "Hello, world\n"
Occasionally we'd like to set some output apart using blank lines. An idiomatic way to write a blank line in Ruby is to use puts
with no argument.
puts
puts
puts "IMPORTANT: Please read this important notice"
puts
puts
# >>
# >>
# >> IMPORTANT: Please read this important notice
# >>
# >>
Most often when we use puts
we are giving it a single string or object to print. But puts
can take an arbitrary number of arguments. Each will be converted to a string and then printed on its own line.
puts "The time is now:", Time.now, "Your lucky number is:", rand(10)
# >> The time is now:
# >> 2013-12-24 11:38:24 -0500
# >> Your lucky number is:
# >> 4
Not only will puts
print each argument on its own line, it will also flatten arrays and print each element of the flattened array on its own line.
puts "Favorite things:", ["Raindrops on roses", ["whiskers on kittens", "bright copper kettles"]]
# >> Favorite things:
# >> Raindrops on roses
# >> whiskers on kittens
# >> bright copper kettles
This means that loops for printing out collections are often superfluous. Here's a loop that prints out the load path of the current Ruby process.
$:.each do |path|
puts path
end
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/site_ruby/2.0.0
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/site_ruby/2.0.0/x86_64-linux
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/site_ruby
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/vendor_ruby/2.0.0
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/vendor_ruby/2.0.0/x86_64-linux
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/vendor_ruby
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0/x86_64-linux
This can be replaced with a simple one-line puts
call.
puts $:
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/site_ruby/2.0.0
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/site_ruby/2.0.0/x86_64-linux
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/site_ruby
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/vendor_ruby/2.0.0
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/vendor_ruby/2.0.0/x86_64-linux
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/vendor_ruby
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0/x86_64-linux
puts
is also smart about printing strings with newlines on the end. If we tell it to print a line with that has a newline already, puts
won't print an extra newline.
puts "Hello, world\n"
# >> Hello, world
This means we can take lines read in from a file and print them directly with puts
without worrying about doubled-up newlines.
lines = File.readlines("/etc/hosts")
# => ["127.0.0.1\tlocalhost\n",
# "127.0.1.1\thazel\n",
# "\n",
# "# The following lines are desirable for IPv6 capable hosts\n",
# "::1 ip6-localhost ip6-loopback\n",
# "fe00::0 ip6-localnet\n",
# "ff00::0 ip6-mcastprefix\n",
# "ff02::1 ip6-allnodes\n",
# "ff02::2 ip6-allrouters\n"]
puts lines
# >> 127.0.0.1 localhost
# >> 127.0.1.1 hazel
# >>
# >> # The following lines are desirable for IPv6 capable hosts
# >> ::1 ip6-localhost ip6-loopback
# >> fe00::0 ip6-localnet
# >> ff00::0 ip6-mcastprefix
# >> ff02::1 ip6-allnodes
# >> ff02::2 ip6-allrouters
In our episodes on threading you might have noticed that when we used puts
in multiple threads, we'd sometimes see the output get mashed up as one thread dumped its output before another thread added a newline. Here's an example with two threads that each write a line to $stdout
10 times. When we run it we can see that the threads sometimes step on each other's toes.
t1 = Thread.new do
10.times do puts "Thread 1 is awesome!" end
end
t2 = Thread.new do
10.times do puts "Thread 2 is the best!" end
end
t2.join
t1.join
$ ruby threads.rb Thread 2 is the best!Thread 1 is awesome! Thread 1 is awesome! Thread 2 is the best! Thread 1 is awesome! Thread 2 is the best!Thread 1 is awesome! Thread 1 is awesome! Thread 1 is awesome! Thread 2 is the best! Thread 1 is awesome! Thread 2 is the best! Thread 2 is the best! Thread 2 is the best! Thread 2 is the best! Thread 2 is the best! Thread 2 is the best! Thread 1 is awesome! Thread 1 is awesome! Thread 1 is awesome!
This happens because puts
adds its newline as a separate write after printing the string. So there's a chance for another thread to be scheduled in between writing the string and the newline.
An interesting side-effect of puts
rules for strings that already have newlines is that printing them doesn't suffer from this two-step write phenomenon. This means that we can fix our mixed-up output by ensuring the printed strings already have newlines on the end.
t1 = Thread.new do
sleep 0.001
10.times do puts "Thread 1 is awesome!\n" end
end
t2 = Thread.new do
sleep 0.001
10.times do puts "Thread 2 is the best!\n" end
end
t2.join
t1.join
$ ruby threads2.rb Thread 1 is awesome! Thread 1 is awesome! Thread 1 is awesome! Thread 1 is awesome! Thread 1 is awesome! Thread 1 is awesome! Thread 1 is awesome! Thread 1 is awesome! Thread 1 is awesome! Thread 1 is awesome! Thread 2 is the best! Thread 2 is the best! Thread 2 is the best! Thread 2 is the best! Thread 2 is the best! Thread 2 is the best! Thread 2 is the best! Thread 2 is the best! Thread 2 is the best! Thread 2 is the best!
And that brings us to the end of our little exploration of advanced puts
usage. I hope you've learned something you didn't know before. Happy hacking!
Responses