Introduction

staying power. Ruby was first released in 1994, so it is not exactly new, but because it was first released in Japan without any English documentation, it is only now being explored by English speakers. In my 30 years in this business I've seen many programming languages come and go, but I've done some recent coding in Ruby and I think it may have — what term shall I use? —Ruby was first released in 1994, so it is not exactly new, but because it was first released in Japan without any English documentation, it is only now being explored by English speakers. Ruby was written by Yukihiro Matsumoto, nicknamed "Matz" online, for the purpose of avoiding some of the more glaring defects in other languages. Unlike Perl, Python and C++, in Ruby everything is an object. And unlike Perl, Ruby programs can still be understood by their authors weeks or months later. Ruby was written by Yukihiro Matsumoto, nicknamed "Matz" online, for the purpose of avoiding some of the more glaring defects in other languages. Unlike Perl, Python and C++, in RubyAnd unlike Perl, Ruby programs can still be understood by their authors weeks or months later.

Playing in Interactive Mode

13.class => Fixnum 13.0.class => Float "my text".class => String [ 1,2,3 ].class => Array String.class => Class String.ancestors => [String, Enumerable, Comparable, Object, Kernel] Class.ancestors => [Class, Module, Object, Kernel] 1.class.ancestors => [Fixnum, Integer, Precision, Numeric, Comparable, Object, Kernel] (The above output was created using the "irb" Interactive Ruby utility, more on this below) On hearing me say "everything is an object" in Ruby, readers may think I'm exaggerating. But:(The above output was created using the "irb" Interactive Ruby utility, more on this below) Matz (or, if you prefer, Matsumoto-san) believes in the Principle of Least Surprise, which can be taken to mean languages should try to act as one would expect, without necessarily consulting a manual, as well as being as consistent as possible. Ruby certainly meets this criterion — to a greater degree than most languages I've learned over three decades, I find Ruby requires minimum time spent poring over instructions, and maximum time spent producing useful, readable code. I think history will judge Ruby kindly, and possibly even identify it as the first computer language that is kind to its users. Matz (or, if you prefer, Matsumoto-san) believes in the Principle of Least Surprise, which can be taken to mean languages should try to act as one would expect, without necessarily consulting a manual, as well as being as consistent as possible. Ruby certainly meets this criterion — to a greater degree than most languages I've learned over three decades, I find Ruby requires minimum time spent poring over instructions, and maximum time spent producing useful, readable code. I think history will judge Ruby kindly, and possibly even identify it as the first computer language that is kind to its users. Before going on, I ask that my readers check for the presence of Ruby on their systems, and if necessary acquire Ruby from the first of the reference links provided at the bottom of this page. If you are running Linux, you may find that Ruby is already installed, or you can get it from your installation CDs or online using your distribution's package utility. Before going on, I ask that my readers check for the presence of Ruby on their systems, and if necessary acquire Ruby from the first of the reference links provided at the bottom of this page. If you are running Linux, you may find that Ruby is already installed, or you can get it from your installation CDs or online using your distribution's package utility. NOTE: This article assumes you are running Linux. Those with other operating systems will probably be able to follow along in a general way, with some minor adjustments to code and procedures. NOTE: This article assumes you are running Linux. Those with other operating systems will probably be able to follow along in a general way, with some minor adjustments to code and procedures. To see if you have Ruby available, open a command shell and type: To see if you have Ruby available, open a command shell and type: $ ruby -v ruby 1.8.2 (2004-12-25) [i386-linux] NOTE: Don't type the '$' symbol above, that is just my way of identifying a shell interpreter context. NOTE: Don't type the '$' symbol above, that is just my way of identifying a shell interpreter context. Now type: Now type: $ irb irb(main):001:0> Now you are in the Interactive Ruby utility, so you can experiment to your heart's content. Try these experiments: Now you are in the Interactive Ruby utility, so you can experiment to your heart's content. Try these experiments: 111111111**2 => 12345678987654321 That's a string of nine '1's, and '**' means "raised to the power" in Ruby. That's a string of nine '1's, and '**' means "raised to the power" in Ruby. Whoa. Isn't "12345678987654321" more than what one should expect from a 32-bit integer? Well, yes, but this is because, when needed, Ruby automatically segues between the Fixnum class and something called "Bignum": Whoa. Isn't "12345678987654321" more than what one should expect from a 32-bit integer? Well, yes, but this is because, when needed, Ruby automatically segues between the Fixnum class and something called "Bignum": (111111111**2).class => Bignum Now try this: Now try this: 111**111 => 10736201288847422580121456504669550195985072399422480 48047759111756250761957833470224912261700936346214661 03743092986967777786330067310159463303558666910091026 01778558729553962214205731543706973022937535754649410 3400699864397711 Wow. Looking at that example, you may be able to guess why cryptologists love Ruby. Now try this: Wow. Looking at that example, you may be able to guess why cryptologists love Ruby. Now try this: 0.upto(32) { |n| print "#{2**n}, " } => 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648, 4294967296, I will leave additional interactive experiments up to my readers' fertile imaginations. :) I will leave additional interactive experiments up to my readers' fertile imaginations. :)

Creating Ruby Executable Files

Now let's create a plain-text file, name it "experiment.rb," make it executable, and put it in any convenient directory. Give it this default content: #!/usr/bin/ruby -w puts "Hello world." Run it like this (remember you don't type the '$', it simply reminds you to get into a command shell): Run it like this (remember you don't type the '$', it simply reminds you to get into a command shell): $ ./experiment.rb Hello World Now let's create something more useful, a simple factoring program (Program 1): Now let's create something more useful, a simple factoring program (Program 1): #!/usr/bin/ruby -w if ARGV .length == 0 puts "Usage: integer to factor" exit 0 end q = ARGV [ 0 ] .to_i qd2 = q /2 n = 2 prime = true while n <= qd2 if q % n == 0 puts n prime = false end n += 1 end puts " #{ q } is prime." if prime Just copy this program from your browser's display and paste it into a suitable text file. Now let's test it: Just copy this program from your browser's display and paste it into a suitable text file. Now let's test it: $ ./experiment.rb 1234567 127 9721 $ ./experiment.rb 104729 104729 is prime. Look carefully at the listing for Program 1 above. I am sure you can figure out that ARGV represents an array of user entries passed to the program. But let's test this assumption in the "irb" Interactive Ruby utility: Look carefully at the listing for Program 1 above. I am sure you can figure out that ARGV represents an array of user entries passed to the program. But let's test this assumption in the "irb" Interactive Ruby utility: ARGV.class => Array I cannot leave this subtopic without mentioning that there is another similar array named "ENV," whose purpose I think you can guess. Try these experiments: I cannot leave this subtopic without mentioning that there is another similar array named "ENV," whose purpose I think you can guess. Try these experiments: ENV.length => 41 (on my system) ENV.each { |key,value| puts "#{key} = #{value}" } (a list of environment variables and values) I hope you are getting the impression that I intend with these examples — that Ruby does what you would expect in most cases, thus meeting its creator's goal of minimizing surprise. I hope you are getting the impression that I intend with these examples — that Ruby does what you would expect in most cases, thus meeting its creator's goal of minimizing surprise. Here is another program (Program 2), one with a twist. It finds palindromes in a list of words: Here is another program (Program 2), one with a twist. It finds palindromes in a list of words: #!/usr/bin/ruby -w # add a new method to the String class class String def isPalindrome(min_length = 5 ) # Match words that: # are not words that repeat one character self !~ /^(\w)\1*$/i && # are long enough to be interesting length >= min_length && # have at least one vowel self =~ /[aeiouyw]/i && # and ... ahem ... are a palindrome self .downcase == reverse .downcase end end # find palindromes in a file def findPalindromes(source,min_len) words = 0 list = Array .new File .foreach (source) { | word | word .chomp! if word .isPalindrome (min_len) list << word end words += 1 } return words, list end min_length = 5 words, list = findPalindromes( "/usr/share/dict/words" ,min_length); puts list .sort.join ( ", " ) puts "words: #{ words } , palindromes: #{ list .length } " Again, copy this program into a suitable text file and run it. If you are not running Linux, you will have to establish a directory path to a dictionary file, but for most Linux users the program will work without any changes. Again, copy this program into a suitable text file and run it. If you are not running Linux, you will have to establish a directory path to a dictionary file, but for most Linux users the program will work without any changes. Now satisfy yourself that it finds palindromes (words that read the same forward and backward, words like "kayak"), then carefully examine the listing. Notice that I seem to be creating a class named "String" near the top of the listing. But in fact, I am adding a custom method named "isPalindrome" to the existing String class. Ruby allows you to add methods to existing classes, including classes that are part of the language itself. Now satisfy yourself that it finds palindromes (words that read the same forward and backward, words like "kayak"), then carefully examine the listing. Notice that I seem to be creating a class named "String" near the top of the listing. But in fact, I ama custom method named "isPalindrome" to the existing String class. Ruby allows you to add methods to existing classes, including classes that are part of the language itself. Notice about this listing that you can tell what is going on without much effort. Unlike Perl, famous for being a write-only language because of its peculiar syntax, and unlike many other languages that would require many more lines of code to accomplish the same result, Ruby programs show a concise efficiency. Notice about this listing that you can tell what is going on without much effort. Unlike Perl, famous for being a write-only language because of its peculiar syntax, and unlike many other languages that would require many more lines of code to accomplish the same result, Ruby programs show a concise efficiency. I have a personal test for languages, based on long experience reading, processing and writing files. My test is: How short is the code to get a named file's entire contents into a string? Here's the Ruby code: I have a personal test for languages, based on long experience reading, processing and writing files. My test is: How short is the code to get a named file's entire contents into a string? Here's the Ruby code: str = File .read ( "/path/file" ) That's pretty short. Placing an entire file into a string isn't always the best thing to do, in particular when dealing with huge files, but for lexical analysis (as one example) it is really the best approach. I find I must regularly slurp up (to borrow Larry Wall's favorite expression for this activity) entire Web pages and then process their contents without regard to line breaks. That's pretty short. Placing an entire file into a string isn't always the best thing to do, in particular when dealing with huge files, but for lexical analysis (as one example) it is really the best approach. I find I must regularly slurp up (to borrow Larry Wall's favorite expression for this activity) entire Web pages and then process their contents without regard to line breaks.

Data Filter

Now for a program (Program 3) that processes a classic data source — records delimited by linefeeds and fields delimited by tabs — and creates a Web page out of the table. Here is the code: #!/usr/bin/ruby -w # filter: TSV data to HTML table class TsvToHTML @@styleBlock = <<- ENDMARK <style type='text/css'> td { border-left:1px solid #000000; padding-right:4px; padding-left:4px; white-space: nowrap; } .cellTitle { border-bottom:1px solid #000000; background:#ffffe0; font-weight: bold; text-align: center; } .cell0 { background:#eff1f1; } .cell1 { background:#f8f8f8; } </style> ENDMARK def TsvToHTML :: wrapTag (data,tag,modifier = "" ) return "< #{ tag } #{ modifier } >" + data + "</ #{ tag } >

" end # wrapTag def TsvToHTML :: makePage (source) page = "" rowNum = 0 source .readlines.each { | record | row = "" record .chomp.split ( "\t" ) .each { | field | # replace blank fields with field .sub! ( /^$/ , " " ) # wrap in TD tag, specify style row += wrapTag(field, "td" , "class=\"" + ((rowNum == 0 )? "cellTitle" : "cell #{ rowNum % 2 } " ) + "\"" ) } rowNum += 1 # wrap in TR tag, add row to page page += wrapTag(row, "tr" ) + "

" } # finish page formatting [ [ "table" , "cellpadding=0 cellspacing=0 border=0" ] , "body" , "html" ] .each { | tag | page = wrapTag( @@styleBlock , "head" ) + page if tag == "html" page = wrapTag(page, * tag) } return page end # makePage end # class # stdin -> convert -> stdout print TsvToHTML .makePage ( STDIN ) This listing would be a lot shorter were it not for the style block at the top, necessary to make the HTML page look the way I want. But even so, for what it accomplishes, this is a very short listing. This listing would be a lot shorter were it not for the style block at the top, necessary to make the HTML page look the way I want. But even so, for what it accomplishes, this is a very short listing. Notice about this program that there are some very handy Ruby-specific methods in use. For example, in this code: Notice about this program that there are some very handy Ruby-specific methods in use. For example, in this code: # finish page formatting [ [ "table" , "cellpadding=0 cellspacing=0 border=0" ] , "body" , "html" ] .each { | tag | page = wrapTag( @@styleBlock , "head" ) + page if tag == "html" page = wrapTag(page, * tag) } Notice that one of the array's elements is itself an array. And if you examine the listing carefully, you will realize that, later on, this array element is applied to a method argument declared as "*tag". As it turns out, in Ruby the asterisk in this declaration means that, if an array is supplied as an argument, the method will expand the array into individual arguments. Then you will notice that the method "wrapTag" will accept an optional argument named "modifier." Notice that one of the array's elements is itself an array. And if you examine the listing carefully, you will realize that, later on, this array element is applied to a method argument declared as "*tag". As it turns out, in Ruby the asterisk in this declaration means that, if an array is supplied as an argument, the method will expand the array into individual arguments. Then you will notice that the method "wrapTag" will accept an optional argument named "modifier." In essence this means my having provided an array instead of a scalar causes a third argument to be supplied to "wrapTag," which is an easy way to specify optional HTML tag arguments. In essence this means my having provided an array instead of a scalar causes a third argument to be supplied to "wrapTag," which is an easy way to specify optional HTML tag arguments. The last line in the program makes this a "filter," a program that receives its data from standard input and that pipes its result to standard output. To use it as written, one would do this: The last line in the program makes this a "filter," a program that receives its data from standard input and that pipes its result to standard output. To use it as written, one would do this: $ ./experiment.rb < data.tsv > output.html If the file "data.tsv" is a database with tabs separating fields and linefeeds separating records, the resulting file "output.html" will be a rather attractive listing of the table's contents. Here's an example of the output: If the file "data.tsv" is a database with tabs separating fields and linefeeds separating records, the resulting file "output.html" will be a rather attractive listing of the table's contents. Here's an example of the output: Title Comment Page Pocket pk Alien Versus Predator 1 Sanaa Lathan, Raoul Bova, Lance Henriksen 1 1 1 Alien Versus Predator 2 Sanaa Lathan, Raoul Bova, Lance Henriksen 1 2 2 Aviator 1 Leonardo DiCaprio, Cate Blanchett, Kate Beckinsale 1 3 3 Aviator 2 Leonardo DiCaprio, Cate Blanchett, Kate Beckinsale 1 4 4 Batman Begins Christian Bale, Michael Caine, Liam Neeson 2 1 5 Beyond The Sea Kevin Spacey, Kate Bosworth 2 2 6 I created this filter because, notwithstanding my having written one of the better-known free HTML editors ( I created this filter because, notwithstanding my having written one of the better-known free HTML editors ( Arachnophilia ), I am rather tired of writing HTML pages, and one of my recent goals in life is to avoid writing HTML by hand, automating page creation whenever possible. I was able to create and test this Ruby page generator in a matter of hours.

Tree Search

find), or by content (grep). As it turns out, if you want to locate files that have both particular names and particular contents, either find or grep can be used, but in neither case is the program suited to the task, and they are difficult to manage when used this way. Under Linux, there are utilities to search for files by name (), or by content (). As it turns out, if you want to locate files that have both particular names and particular contents, eitherorcan be used, but in neither case is the program suited to the task, and they are difficult to manage when used this way. Because of languages like Ruby, these older utilities seem dated, indeed their presence and behavior reminds programmers of the older, hierarchical structure of computer programming, where an end-user's requirements were met by someone wearing a white lab coat and living behind glass. Because of languages like Ruby, these older utilities seem dated, indeed their presence and behavior reminds programmers of the older, hierarchical structure of computer programming, where an end-user's requirements were met by someone wearing a white lab coat and living behind glass. When I want to search for files based on both name and content, I would prefer not to use a program that has a staggering number of command-line options, seemingly written by someone intent on creating a miraculous glove meant to fit any conceivable hand: Program Command-line options grep 47 find 89 Having tried to use both find and grep for something that neither is particularly good at, daunted by trying to remember the meaning of all those command-line options, and recently inspired by learning Ruby, I decided to write something a little easier to use. Here it is: When I want to search for files based on both name and content, I would prefer not to use a program that has a staggering number of command-line options, seemingly written by someone intent on creating a miraculous glove meant to fit any conceivable hand:Having tried to use bothandfor something that neither is particularly good at, daunted by trying to remember the meaning of all those command-line options, and recently inspired by learning Ruby, I decided to write something a little easier to use. Here it is: #!/usr/bin/ruby -w require "find" if( ARGV .length == 0 ) puts "usage: path -f (fn filter) -s (search str) -h (strip HMTL) -v (rev. match)" exit 0 end # default values path = "." # search path fnFilter = ".*" # filename filter search = ".*" # search contents for this reverse = false # list only no-match files striptags = false # strip HTML tags before match oldarg = "" # process command-line args ARGV .each { | arg | if path .length == 0 && FileTest .exist? (arg) # path path = arg elsif oldarg == "-f" # filename filter fnFilter = arg elsif oldarg == "-s" # search string search = arg elsif arg == "-v" # reverse search reverse = true elsif arg == "-h" # strip HTML tags striptags = true end oldarg = arg } files = 0 matches = 0 begin Find .find (path) { | fn | # if fn is a file and if it matches filter if FileTest .file? (fn) && fn =~ / #{ fnFilter } / files += 1 # read the file data = File .read (fn) # strip HTML tags if striptags data .gsub! ( /<.*?>/ , "" ) end # look for search string r = (data =~ / #{ search } /i )?true :false # test for reverse logic if r != reverse puts fn matches += 1 end end } puts "Files #{ files } , Matches #{ matches } " rescue StandardError puts "Error: #{ $! } " end Granted this program's limitations, it has the advantage that, if I forget how it works, I can look at its plain-text source, and I can change how it behaves in a flash. It also has the advantage that, if I should lose the source, it might take 20 minutes to recreate it. Granted this program's limitations, it has the advantage that, if I forget how it works, I can look at its plain-text source, and I can change how it behaves in a flash. It also has the advantage that, if I should lose the source, it might take 20 minutes to recreate it. I can't leave this example without explaining that it touches on one of my favorite issues: the gradual breakdown of the distinction between writing programs and using programs. Ruby makes it so easy to create task-specific code that I think much of the archive of pre-written utilities may in time wither away from lack of use. I can't leave this example without explaining that it touches on one of my favorite issues: the gradual breakdown of the distinction between writing programs and using programs. Ruby makes it so easy to create task-specific code that I think much of the archive of pre-written utilities may in time wither away from lack of use.

Read, Edit, Write

This represents another task I find myself doing again and again. I sometimes must search a tree of files and edit each of them in the same way — many files, one change. I've used Perl for this in the past, but the Ruby code is much cleaner and easier to understand and modify: #!/usr/bin/ruby -w require "find" path = "/path/to/files" Find .find (path) { | f | if f =~ /\.html?$/ # if it's an HTML page data = File .read (f) # read the file # put the desired regexp here changed = data .gsub ( /find/ , "replace" ) # write only changed files if(changed != data) File .new (f, "w" ) .write (changed) end end } People who think about using programs like this one should be aware that it can wipe out an entire directory tree of files if given an ill-constructed regular expression, so always make a backup of the files being processed. People who think about using programs like this one should be aware that it can wipe out an entire directory tree of files if given an ill-constructed regular expression, so

Network Examples

Now for a program (Program 4) that can read Web pages and other content from the Internet. Here is the listing: #!/usr/bin/ruby -w require 'net/http' resp = Net :: HTTP .new ( 'www.google.com' , 80 ) .get ( '/' , nil ) puts "Code = #{ resp .code } , Message = #{ resp .message } " resp .each { | key, val | printf "%-18s = %-40.40s

" , key, val } puts "Data length: #{ resp .body.length } " print "Show data (y/n):" reply = STDIN .readline.chomp! if reply =~ /[Yy]/ puts resp .body end If you run this program, you will see something like this: If you run this program, you will see something like this: Code = 200, Message = OK cache-control = private date = Thu, 29 Dec 2005 03:17:30 GMT content-type = text/html server = GWS/2.1 set-cookie = PREF=ID=05b7af4fc753f4ec:TM=1135826250:L transfer-encoding = chunked Data length: 2518 Show data (y/n): Now that's a small program with a big result! In fact, if you look at the listing carefully, you'll realize that all the lines past this line — resp = Net :: HTTP .new ( 'www.google.com' , 80 ) .get ( '/' , nil ) — are merely ways to process the data acquired in that one line. Now that's a small program with a big result! In fact, if you look at the listing carefully, you'll realize that all the lines past this line —— are merely ways to process the data acquired in that one line. I have used this program to quickly scan through long lists of off-site URLs to verify that they are valid, or, if not, why not. This is a big improvement over the old way of verifying URLs, which I will leave up to the reader's imagination. I have used this program to quickly scan through long lists of off-site URLs to verify that they are valid, or, if not, why not. This is a big improvement over the old way of verifying URLs, which I will leave up to the reader's imagination. Moving right along, here is a listing (Program 5) that represents a complete HTTP server: Moving right along, here is a listing (Program 5) that represents a complete HTTP server: #!/usr/bin/env ruby # this HTTP server logs its actions to stdout require 'webrick' include WEBrick # Create server server = HTTPServer .new ( :Port => 8000 , :DocumentRoot => "/path/DocumentRootDirectory" ) # When the server gets a control-C, kill it trap ( "INT" ) { server .shutdown } # Start the server server .start You may not believe this, but if you run the program in listing 5 (editing the listing to point to a directory with one or more Web pages), then run your favorite browser like this — firefox localhost:8000 — this mini-server will supply the browser with your Web pages, and it will also print a running account of what it is doing. This is a terrific way to discover what HTTP servers do behind the scenes. You may not believe this, but if you run the program in listing 5 (editing the listing to point to a directory with one or more Web pages), then run your favorite browser like this —— this mini-server will supply the browser with your Web pages, and it will also print a running account of what it is doing. This is a terrific way to discover what HTTP servers do behind the scenes.

Conclusion

This is meant to be a quick introduction to Ruby, and hopefully to encourage my readers to familiarize themselves with this terrific language. Please click some of the links in the resources section below to get a better grasp of Ruby. I must say, having started out writing assembly-language programs for the Apple II in the mid-1970s and having sampled most of the newer languages since then, I am amazed at the speed with which I can create working code using Ruby. I am also amazed at the number of programs I've written over the years that I cannot even run any more, including an embarrassing number of comparatively recent Java programs that are no longer compatible with current Java runtime engines, something that should not have happened, at least not so quickly. I must say, having started out writing assembly-language programs for the Apple II in the mid-1970s and having sampled most of the newer languages since then, I am amazed at the speed with which I can create working code using Ruby. I am also amazed at the number of programs I've written over the years that I cannot even run any more, including an embarrassing number of comparatively recent Java programs that are no longer compatible with current Java runtime engines, something that should not have happened, at least not so quickly. My point? I think it's unavoidable that programs will go out of date, either because they can no longer be compiled or run, or because the problem they were meant to address has disappeared, or both. Because of this reality, I think it's a good idea to minimize the effort required to write computer programs, since history tells us they will have to be written over again. In my view, Ruby represents a big step toward that goal. My point? I think it's unavoidable that programs will go out of date, either because they can no longer be compiled or run, or because the problem they were meant to address has disappeared, or both. Because of this reality, I think it's a good idea to minimize the effort required to write computer programs, since history tells us they will have to be written over again. In my view, Ruby represents a big step toward that goal. I am not recommending Ruby as an across-the-board replacement for such core languages as C++ (at the time of writing Ruby is interpreted, not compiled). I do think it is a much better choice than either Java or Perl, for the kinds of projects these languages are applied to. I am not recommending Ruby as an across-the-board replacement for such core languages as C++ (at the time of writing Ruby is interpreted, not compiled). I do think it is a much better choice than either Java or Perl, for the kinds of projects these languages are applied to. I also think Ruby speaks for itself, eloquently. It creates a nicer impression when used than any article can produce, including this one. :) I also think Ruby speaks for itself, eloquently. It creates a nicer impression when used than any article can produce, including this one. :) Be sure to browse the drop-down lists on this page for more Ruby code examples.

References