A very simple tool for Ruby debugging is Kernel.p, the p
method of the Kernel
class.
The expression p obj invokes the inspect method of the object obj and
prints the resulting string.
Let's contrast p and puts:
>> s = " a\ntest\there... " >> puts s a test here... => nil >> puts [s, s.size.to_s] a test here... 17 => nil >> p s " a\ntest\there... " >> p [s, s.size.to_s] [" a\ntest\there... ", "17"]Note that inspect applies recursively—inspecting [s, s.size.to_s] causes s.inspect and s.size.to_s.inspect to be done.
Here's tac.rb
, which we wrote in class, with a call to Kernel.p
added at the bottom of the loop:
$ cat tac.rb reversed = "" while line = gets reversed = line + reversed p ["bottom of loop; l,r", line, reversed] end puts reversedHere's an input file:
$ cat lines line 1 second line the endLet's run it:
$ ruby tac.rb < lines ["bottom of loop; l,r", "line 1\n", "line 1\n"] ["bottom of loop; l,r", "second line\n", "second line\nline 1\n"] ["bottom of loop; l,r", "the end\n", "the end\nsecond line\nline 1\n"] the end second line line 1We see that the bottom of the loop was reached three times. The values of
line
and reversed
are shown each time.
The string "bottom of loop; l,r"
is simply a label to show me where the output is coming from;
the "l,r"
reminds me that the values of line
and reversed
follow.
I concisely aggregate them by putting them in an array.
I could produce nicer output with a puts
like this,
puts "bottom of loop; line = #{line.inspect}, reversed = #{reversed.inspect}"but throwing values of interest into an array with a string as a label is usually good enough for debugging.
I personally find that adding a p [...whatever...] in a place or two is sufficient for most of my debugging needs, especially if I've tested my methods individually.
byebug
byebug is a simple but useful Ruby debugger. There's a nice introduction to it section 16.4 in The Ruby Way, 3rd edition by Hal Fulton. Here's a link to that section on Safari.
One very handy byebug capability that Fulton doesn't mention is that if the text you enter at the (byebug)
prompt isn't recognized as a byebug
command, it's evaluated as a Ruby expression and the result is printed.
In the example below I first enter c 3, a byebug
command to run to line three.
Then I evaluate the expressions line
, line.chomp
, and line[-1].ord
.
$ byebug tac.rb [1, 6] in /Users/whm/372/ruby/tac.rb => 1: reversed = "" 2: while line = gets 3: reversed = line + reversed 4: # p ["bottom of loop", line, reversed] 5: end 6: puts reversed (byebug) c 3 testing Stopped by breakpoint 1 at /Users/whm/372/ruby/tac.rb:3 [1, 6] in /Users/whm/372/ruby/tac.rb 1: reversed = "" 2: while line = gets => 3: reversed = line + reversed 4: # p ["bottom of loop", line, reversed] 5: end 6: puts reversed (byebug) line "testing\n" (byebug) line.chomp "testing" (byebug) line[-1].ord 10
byebug
vs. reading standard inputPrograms that rely on redirection of standard input, like tac.rb and xfield.rb aren't handled well by byebug. With a command like
byebug tac.rb < somefilebyebug interprets the contents of somefile as byebug commands!
I haven't found a simple way to direct byebug to read commands from the console (i.e., /dev/tty) instead of standard input, but here's a technique that seems to work:
if stdin_fname = ENV["stdin"] then # ENV is a Ruby Hash, like a Java Map $stdin = open(stdin_fname) end reversed = "" while line = gets reversed = line + reversed end puts reversed
$ ruby tac.rb < lines the end second line line 1
$ stdin=lines byebug tac.rbThat stdin=lines construct tells Bash to create an environment variable named stdin that is to endure only for the the command that follows.
Let's try it:
$ stdin=lines byebug tac.rb [1, 9] in /p1/hw/whm/372/tac.rb => 1: if stdin_fname = ENV["stdin"] then # ENV is a Ruby Hash, like a Java Map 2: $stdin = open(stdin_fname) 3: end 4: 5: reversed = "" 6: while line = gets 7: reversed = line + reversed 8: end 9: puts reversed (byebug) c 9 Stopped by breakpoint 1 at /p1/hw/whm/372/tac.rb:9 [1, 9] in /p1/hw/whm/372/tac.rb 1: if stdin_fname = ENV["stdin"] then # ENV is a Ruby Hash, like a Java Map 2: $stdin = open(stdin_fname) 3: end 4: 5: reversed = "" 6: while line = gets 7: reversed = line + reversed 8: end => 9: puts reversed (byebug) reversed "the end\nsecond line\nline 1\n" (byebug) reversed.size 27 (byebug) c the end second line line 1 $For more on byebug see also https://github.com/deivid-rodriguez/byebug. Googling turns up lots of stuff, too, of varying correctness and merit.