Tuesday, June 06, 2006

Ruby Kernel system, exec and %x

The Ruby Core Library documentation is very similar for Kernel.system, Kernel.exec and %x[..]. Recently I needed to kick off a system process, so I spent some time working with all 3 options.

Kernel.exec does exactly what the documentation states:
Replaces the current process by running the given external command. If exec is given a single argument, that argument is taken as a line that is subject to shell expansion before being executed. If multiple arguments are given, the second and subsequent arguments are passed as parameters to command with no shell expansion. If the first argument is a two-element array, the first element is the command to be executed, and the second argument is used as the argv[0] value, which may show up in process listings. In MSDOS environments, the command is executed in a subshell; otherwise, one of the exec(2) system calls is used, so the running command may inherit some of the environment of the original program (including open file descriptors).

exec "echo *" # echoes list of files in current directory
# never get here

exec "echo", "*" # echoes an asterisk
# never get here
An important thing to notice is that it replaces the current process. To immediately see what is meant by 'replaces', try running exec from irb:
focus:~/work/eaa jay$ irb
irb(main):001:0> exec 'svn st'
focus:~/work/eaa jay$
A more realistic example could be, assume you wanted to execute an external command from rake and you tried to use exec. Your external command would be executed, but rake would never finish. Not exactly desired results.

Kernel.system behaves very similarly, but does not replace the current process. The documentation states the following:
Executes cmd in a subshell, returning true if the command was found and ran successfully, false otherwise. An error status is available in $?. The arguments are processed in the same way as for Kernel::exec.

system("echo *")
system("echo", "*")

produces:

config.h main.rb
*
The same irb test shows us that system will return true or false, and $? can be used to get a Process::Status instance.
irb(main):014:0> s = system 'uptime'
10:56 up 3 days, 23:10, 2 users, load averages: 0.17 0.17 0.14
=> true
irb(main):015:0> s.class
=> TrueClass
irb(main):016:0> $?.class
=> Process::Status
Process::Status does contain the exitstatus Fixnum, but it does not appear to capture any errors that may occur.
irb(main):019:0> s = system "ruby -e 'invalid ruby $%^&'"
-e:1: parse error, unexpected $undefined., expecting $
invalid ruby $%^&
^
=> false
Though I do like knowing my external application failed, it is nice to know why it failed. If you can redirect the output to a file this isn't a problem; however, you have 2 options if the execution output is simply dumped to stdout. The first option is to use system and redirect output to a file
irb(main):003:0> system 'uptime > results.log'
=> true
irb(main):004:0> exit
focus:~/work/eaa jay$ more results.log
13:09 up 4 days, 1:23, 2 users, load averages: 0.21 0.24 0.19
Another option is to use %x[..] and save the results. This option is more often used when the results need to be captured and used to determine further execution flow. To capture the results of a %x[..] you need to assign a variable to the return value.
irb(main):001:0> result = %x[uptime]
=> "13:16 up 4 days, 1:30, 2 users, load averages: 0.39 0.29 0.23\n"
irb(main):002:0> p result
"13:16 up 4 days, 1:30, 2 users, load averages: 0.39 0.29 0.23\n"
Using system allows you to deal with the output when false is returned. However, if you are always going to open the file and process the results you are likely better off using %x[..] in the first place.
Post a Comment