Once again, I put the Rails tutorial on the back burner (sorry folks :-/) for a coding project. This one wasn’t quite so little as the last one, though. While thoughts went through my mind about writing something similar to JAXB called RAXB, and I created the project on Software Without Incident and all, that’s not what I ended up working on. Instead, my focus the last month or so has been RubyShell: a UNIX shell written in Ruby with Ruby as its primary language. I’m ready now to make an initial release, so here it is: RubyShell 0.1!

Before I go on, I should mention that there has been a previous attempt at a Ruby shell; it was called ruSH and sits on RubyForge. Unfortunately, its last release was on August 28, 2005, four days after its first release. It was announced on ruby-talk and everything, but for some reason it seems to have died. The domain where it was being hosted (off RubyForge) seems to have similarly expired. That said, I only briefly glanced at the ruSH code while writing what I did, and didn’t really use any of what I saw (though I might look there again for inspiration in the future).

Currently, RubyShell can do a few things:
  • Run external commands. Currently, this is done through Kernel#system most of the time, which may be a bit of a cop-out; however, I believe it allows most of these commands to run on Windows. Recent revisions to the way I’m using Open3 may have dispelled these concerns, though. Using Kernel#system also gives the ability to run a program like vim without a problem, while running it with Open3 causes serious issues.
  • Display a prompt (I know, it’s basic, but it’s there :)).
  • Run single-line Ruby (including semicoloned stuff). There’s currently no parser or anything, instead we use the defined? keyword, as detailed in this post on RedHanded. Sadly, as I mentioned, this means only single-line Ruby is currently supported. Whether support for multiline Ruby will come via a parser or via some quiet hackery that will allow common multiline constructs (e.g., def, class, if, etc.) is up for debate at this point.
  • Connect commands with pipes. This is the exception to the Kernel#system comment above. When output is being piped, external commands are run through Open3 to capture their output. Ruby code in piped commands has access to the output of the last command via the $pipe variable. Alternate syntaxes are in the works; I’ve written up some ideas as part of the RubyShell Implementation Concepts/Ideas document on SWI.

That’s about all I can think of right now. Some things are hacked together in a nasty way; the way a PassthroughCommand with output capturing is monitored for completion comes to mind (currently I’m using Thread.list.length, which is just stupid).

I’ll be trying to establish a RubyForge project for RubyShell, but in the meantime it can be found, as with all my tinkerings, on Software Without Incident. The RubyShell project page is here, and the release files are here. Comments are welcome, help is welcome.

I’ll be off on vacation starting on Saturday for about a week, but I’ll try and keep an eye out to catch comments and such.

Enjoy!

UPDATE: I’ve had a chance to try it on Windows, and it looks like Kernel#system there handles command-line arguments in a weird way. dir /w, my test, didn’t seem to work, while just plain dir did. Regardless, pipes still don’t work there, unsurprisingly. This is because a gets runs at least once when the first command in a list of piped commands isn’t Ruby code. On Windows, this gets blocks the application (go Windows! Not…), thus disallowing useful things like the termination check from running. Even commands like dir will block it, because the gets runs whether the command expects input or not. As far as I know, there’s no way to differentiate between commands that do and don’t expect input.

I suspicion that it might be possible to use Kernel#select to see if the application wants input, but I seem to recall experimenting and seeing that it returned even if input wasn’t expected. Not only that, but it’ll register as ready for reading and writing. Nonetheless, I’ll see if I can’t try and figure something out at some point to alleviate that issue. It would be kind of rockin’ to have piping on Windows (outside of cygwin and such) :)

3 Responses to “A Ruby Shell? Sure! RubyShell!”

  1. jc Says:

    Brilliant! Best of luck with this project. Wish I could help, but I’m a complete unix incompetent. If my shell (currently bash) supported ruby, I wouldnt have half as many problems as I do on a daily basis.

  2. Mr eel Says:

    This is a fantastic idea! When writing scripts it’s east enough to substitute Bash for Ruby, but it would be awesome if I could swap my bash functions for ruby code. I can imagine doing some really cool stuff.

    I’m going to download it and have a poke around today.

  3. Shadowfiend Says:

    Sounds great! Let me know what you think, and any ideas for improvement.

    Keep in mind, by the way, that currently only single-line Ruby is supported (due to the way RubyShell checks whether code should be evaluated as Ruby or as a command). I’m still not 100% certain how I’m going to deal with this—if you’ve got any ideas, I would love to hear them!

    Similarly, I’d love to hear some thoughts on the various piping ideas I put forth in the linked document above.

Leave a Reply