Ruby 101: Substrings in Ruby 17

Posted by jeff Sunday, August 19, 2007 20:21:00 GMT

Photo credit

The other day I was wondering why some really simple Ruby code of mine wasn’t working. The code under test looked something like this:


    def find_directories
        directories = []
        entries = `ls -l`.split/\n/  # get long-format directory listing
        entries.shift   # toss away the totals line
        entries.each do |entry|
            is_directory = (entry[0] == 'd')  # is it a directory?
            directories << dir if is_directory
        end
        directories
    end

I figured that my ls -l.split/\n/ trickery wasn’t working. So I fired up irb and tried it manually. It worked fine.

Then I sprinkled a few puts statements throughout. entries was fine, and each entry was perfect, looking something like this:

drwx------ 24 jcohen jcohen 816 Aug 19 11:04 Documents

I couldn’t figure out what was wrong. All I had to do was look at the first character of entry; if it was a ‘d’, then I knew I had a directory. But for some reason, is_directory was always false. I googled, checked the RDocs for the String class, thumbed through the PickAxe, and something I read triggered one of those ah-ha moments.

Rats: My C# brain is still alive and kicking.

To confirm my fears, I fired up irb again:


irb> s = "Jeff" 
=> "Jeff" 
irb> s[0]
=> 74

That’s right, folks: on a string, the bracket syntax – when given only one parameter – will return the ASCII value of the character inside. Not the actual character, as it will in C#.

To correctly grab a one-character substring from a string, you have to supply two parameters:


irb> s[0,1]
=> "J" 

Comments

Leave a response

  1. Matt Blodgett   August 19, 2007 @ 09:22 PM

    Which way do you prefer? The C# way makes a lot more sense to me.

    This may be the first time (in my limited experience with Ruby) that I have seen the way Ruby does something and thought to myself, "That's stupid."

  2. zuwiki   August 19, 2007 @ 09:52 PM

    Yeah, I ran into that problem myself. xD

  3. Chris O'Sullivan   August 19, 2007 @ 09:59 PM

    Ay carumba - I haven't come across that yet myself, but you can be sure that I would've done it the "C# Way"

    Thanks Jeff!

  4. Chad Humphries   August 19, 2007 @ 10:38 PM

    If you are in rails you can use the ActiveSupport helper at:

    s = "Jeff"

    s.at(0) => J

    It's basically a wrapper for what you did, but a nice one

  5. Jeremy L   August 20, 2007 @ 12:16 AM

    I definitely wouldn't say "that's stupid", it just that it can be easily forgotten. The way that I would have fixed the bug is to use '?':

    entry[0] == ?d

  6. Oskar Austegard   August 20, 2007 @ 01:04 AM

    Is there really no debugger for Ruby?

  7. Phillip   August 20, 2007 @ 02:11 AM

    Jeremy L:

    That's interesting. I'm no ruby guru by any stretch of even the most incredible imagination, so there's a lot that I don't know. But in all of my browsing and learning, I haven't seen that one. What's it called and where can I learn more?

    Thanks, Phillip

  8. Andrew Montgomery   August 20, 2007 @ 05:59 AM

    I believe you can also do:

    'abc'[0].chr #=> 'a'

  9. tilo   August 20, 2007 @ 06:43 AM

    Yet another take on it could be to use a regular expression (see class String in stdlib, http://www.ruby-doc.org/stdlib/download.html )

    ruby -e 'p "a"[/^d$/]; p "d"[/^d$/];'

    For matching substrings the Ruby regex engine also has \B, http://snippets.dzone.com/posts/show/1352

    Just my 2 cents.

  10. JK   August 20, 2007 @ 12:44 PM

    s[0..0] # "J" s[0,1] # "J" s[0].chr # "J"

  11. Jeremy Weiskotten   August 20, 2007 @ 01:29 PM

    It looks like this behavior changes in Ruby 1.9. String#[] returns a String, not an ASCII value.

    http://eigenclass.org/hiki.rb?Changes+in+Ruby+1.9#l104

  12. Lou C   August 20, 2007 @ 04:17 PM

    Jeremy is right, I am not sure about 1.9, but in Ruby 2.0 it will behave like expected.

    For now, the second parameter tells Ruby that you want a String returned, and the number specifies the length of that substring.

    I remember browsing through the changelog for the bleeding edge Ruby, and I saw this behavior explicitly marked Ruby2.0 by Matz which means this is something that will definitely be changed for 2.0, maybe even 1.9 like Jeremy said.

  13. chris   August 20, 2007 @ 06:40 PM

    If you use the shell anyway:

    find <dir> -type d

    will list you the directories.

    Or, if you use the zsh: ls */(/)

  14. Phillip   August 20, 2007 @ 07:03 PM

    Jeff,

    I'm a little slow sometimes, but I got to thinking, why didn't you use the File.directory? function? You could have simplified your code as:

    directories << entry if File.directory?(entry)

    That's neater and more portable.

    Just a thought.

  15. Jeff   August 20, 2007 @ 08:50 PM

    To everyone regarding why I didn't use File.directory? instead:

    Unfortunately the sample I posted wasn't exactly what I was working on. In reality I'm using the Net::FTP class, and it returns directory information as a series of entries that are formatted to look exactly like what you get when you do a ls -l on Unix.

    Too lazy to actually dig out the real code (and also concerned that the FTP stuff would add noise to the point of the post), I instead showed my draft code which used ls -l when I was generating some test data for my tests.

    But points to everyone who realized there was a pure-Ruby way to accomplish the same thing.

    And if there's a way to do this with the FTP class that I'm not aware of, please let me know.

  16. kino   May 24, 2008 @ 01:12 AM

    As is evident upon close examination, our judgements are the mere results of the power of the Ideal, a blind but indispensable function of the soul.

  17. Ellroy   May 30, 2008 @ 11:22 PM

    Has it ever been suggested that the reader should be careful to observe that there is a causal connection between the things in themselves and the never-ending regress in the series of empirical conditions?

Comment


(won't be published)