Ruby 101: Substrings in Ruby 17

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"




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."
Yeah, I ran into that problem myself. xD
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!
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
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
Is there really no debugger for Ruby?
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
I believe you can also do:
'abc'[0].chr #=> 'a'
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.
s[0..0] # "J" s[0,1] # "J" s[0].chr # "J"
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
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.
If you use the shell anyway:
find <dir> -type d
will list you the directories.
Or, if you use the zsh: ls */(/)
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.
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 -lon 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 -lwhen 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.
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.
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?