14.9. Miscellaneous Scripting TasksThere are a few tidbits left over. These are uncreatively classified as "miscellaneous." 14.9.1. Single-File Ruby SolutionsThere are occasions where you may want a quick or temporary Ruby installation. You might even want to distribute Ruby along with your program as a single executable. We've already looked at Ruby Installer, the "one-click installer" for Windows platforms. There are plans (immature at the time of this writing) to make Linux and Mac OSX versions of the installer. Erik Veenstra has also made significant progress in packaging Ruby itself as well as Ruby applications. He is the author of AllInOneRuby, Tar2RubyScript, and RubyScript2Exe (all of which can be found on his site at http://www.erikveen.dds.nl). AllInOneRuby is a single-file Ruby installation. It packages all of the Ruby interpreter, core, and standard libraries in a single archive that is easy to move or copyit can be stored on a USB drive, for example, so that you can carry an entire Ruby installation in your pocket and "install" it on a new machine in a fraction of a second. It runs on Windows or Linux and has experimental support for Mac OSX. Tar2RubyScript is exactly what the name implies. It takes a directory structure containing your application and creates a self-extracting program (consisting of a Ruby program with a tar archive appended). This is similar to Java's concept of a JAR file. The script to be run should be named init.rb; if the stored item is a library rather than a standalone application, this file can be omitted. RubyScript2Exe is perhaps slightly misnamed. It does indeed transform an entire Ruby application into a single binary file, but it is cross-platform, running on Windows, Linux, or Mac OSX. You can think of it as a "compiler," but of course it isn't a real one. It collects files from your Ruby installation to produce the package, so there is no need (or possibility) of cross-compiling. Be aware that the executable you get is "stripped" in the sense that any unneeded Ruby libaries are left out. If you use Tar2RubyScript, the archive created is runnable on any system that has Ruby (and the application's dependencies) installed. RubyScript2Exe doesn't have this limitation because it includes (along with your application) the entire Ruby interpreter and runtime environment and all dependencies. You can use these two tools together or separately. 14.9.2. Piping into the Ruby InterpreterBecause the Ruby interpreter is a single-pass translator, it is possible to pipe code into it and have it executed. One conceivable purpose for this is to use Ruby for more complex tasks when you are required by circumstance to work in a traditional scripting language such as bash. Listing 14.6, for example, is a bash script that uses Ruby (via a here-document) to calculate the elapsed time in seconds between two dates. The Ruby program prints a single value to standard output, which is then captured by the ksh script. Listing 14.6. bash Script Invoking Ruby
Note that the two input values in this case are passed as environment variables (which must be exported). The two lines that retrieve these values could also be coded in this way: time1="$time1" # Embed the shell variable directly time2="$time2" # into a string... However, the difficulties are obvious. It could get very confusing whether a certain string represents a ksh variable or a Ruby global variable, and there could be a host of problems with quoting and escaping. It's also possible to use a Ruby "one-liner" with the -e option. Here's a little script that reverses a string using Ruby: #!/usr/bin/bash string="Francis Bacon" ruby -e "puts '$string'.reverse" | read reversed # $reversed now has value "nocaB sicnarF" UNIX geeks will note that awk has been used in a similar way since time immemorial. 14.9.3. Getting and Setting Exit CodesThe exit method will raise a SystemExit exception and ultimately return the specified exit code to the operating system (or to the calling entity). This is a Kernel method. There is also a method exit! that differs in two ways: It doesn't run the exit handlers before quitting, and the default return value is -1. # ... if (all_OK) exit # Normally (0) else exit! # In a hurry (-1) end When a Ruby return code is retrieved by the operating system (for example, by doing echo $?), it is seen as the same integer specified in the code. When a subprocess exits and we use wait2 (or waitpid2) to examine the return code, we will find it left-shifted by eight bits. This is a POSIX quirk that Ruby has inherited. child = fork { sleep 1; exit 3 } pid, code = Process.wait2 # [12554,768] status = code << 8 # 3 14.9.4. Testing Whether a Program Is Running InteractivelyA good way to determine whether a program is interactive is to test its standard input. The method isatty? (historically, "is a teletype") will tell us whether the device is an interactive one as opposed to a disk file or socket. (This is not available on Windows.) if STDIN.isatty? puts "Hi! I see you're typing at" puts "the keyboard." else puts "Input is not from a keyboard." End 14.9.5. Determining the Current Platform or Operating SystemIf a program wants to know what operating system it's running on, it can access the global constant RUBY_PLATFORM. This will return a cryptic string (usually something like i386-cygwin or sparc-solaris2.7), telling the platform on which the Ruby interpreter was built. Because we primarily use UNIX variants (Solaris, AIX, Linux) and Windows variants (98, NT, 2000, XP), we've found the following crude piece of code to be useful. It will distinguish between the UNIX family and the Windows family of operating systems (unceremoniously lumping all others into "other"). def os_family case RUBY_PLATFORM when /ix/i, /ux/i, /gnu/i, /sysv/i, /solaris/i, /sunos/i, /bsd/i "unix" when /win/i, /ming/i "windows" else "other" end end This little set of regular expressions will correctly classify the vast majority of platforms. Of course, this is only a clumsy way of determining how to handle OS dependencies. Even if you correctly determine the OS family, that might not always imply the availability (or absence) of any specific feature. 14.9.6. Using the Etc ModuleThe Etc module retrieves useful information from the /etc/passwd and /etc/group files. Obviously this is only useful in a UNIX environment. The getlogin method will return the login name of the user. If it fails, getpwuid might work (taking an optional parameter, which is the uid). myself = getlogin # hal9000 myname = getpwuid(2001).name # hal9000 # Without a parameter, getpwuid calls # getuid internally... me2 = getpwuid.name # hal9000 The getpwnam method returns a passwd struct, which contains relevant entries such as name, dir (home directory), shell (login shell), and others. rootshell = getpwnam("root").shell # /sbin/sh At the group level, getgrgid or getgrnam behave similarly. They will return a group struct consisting of group name, group passwd, and so on. The iterator passwd will iterate over all entries in the /etc/passwd file. Each entry passed into the block is a passwd struct. all_users = [] passwd { |entry| all_users << entry.name } There is an analogous iterator group for group entries. |