Subscribe:

Pages

Interacting with the Command Line from Ruby

One of the strengths of Ruby is as a scripting language, that is, a language for managing control of other programs. A typical setting is to provide our script with some parameters which are then used to set up and run other programs. For example, we could control the number of times the external program is called, or collect the results and store them in specific files. This kind of automation can help speed up our work considerably, and also lets us think of our research at a higher level of abstraction: i.e. just create a pool of training data as big as is needed, rather than having to laboriously construct every item in the pool by hand. Ruby is also highly suited to this task, as many of the tasks, such as parsing input arguments or formatting strings for output display, are easier in Ruby than in, for example, C.

To achieve this level of control, we first need some way for our Ruby script to handle command-line arguments provided to it. Second we need to be able to construct and run commands from within Ruby just as if we had typed them at the command line.


For example, I am working on the analysis of what are known as k-SAT landscapes: the idea is to construct a boolean function, such as x_1 & x_2 | x_1 & x_3, which contains a given number, m, of clauses, each clause with k terms. The clauses are constructed from n different variables, and there should be N of the landscapes for any given combination of n, m and k. I have a C program which will take n, m, and k as input arguments and print the landscape to standard output. I want to automate the construction of the N landscapes, and capture the output into files named in the format: 'results-n-m-k-i.txt', where i will range from 0 to N-1. This is ideal for automation, as the construction of any landscape can take up to a day.

Basic Techniques

When you start a Ruby program, using: 'ruby program.rb X ...' you can provide command-line arguments as 'X ...'. These arguments are placed into the array ARGV, as strings. (Warning for C programmers: ARGV[0] is the first argument, not the name of the program!) It is a simple matter to read each item out of ARGV, convert to a suitable number format, and place the result into an internal variable: to convert the first parameter to an integer and store in 'x', write: x = ARGV[0].to_i

Ruby strings make it easy to interpolate values from variables using the #{} syntax, which interprets anything inside the brackets and puts the result into the string. For example:

5.times do |i|
puts "The value is #{i}"
end

Will print:

The value is 0
The value is 1
The value is 2
The value is 3
The value is 4

Finally, to call an external program, and pass arguments to it, Ruby provides convenient backticks, ` `. Anything between the backticks will first be interpreted as a string (so variable values can be added in, as above) and then passed to the shell for execution, just as if you had typed the command within the backticks on the command line. Thus, `wc #{filename}` will call the word-count program on the file named within the 'filename' variable.

My final code is below. Notice the check for the number of input arguments, and output of a help message if there are not enough; this is always useful, as I very quickly forget what those input arguments should be. The script can be run easily using:

ruby create-samples.rb 20 50 3 20

# Automate the construction of landscapes
#
# ruby create-samples.rb n m k N
#
# where n = number of variables
# m = number of clauses
# k = number of variables per clause
# N = number of formulae
#
# Results for formulae constructed with names: results-n-m-k-i.txt
# where i runs from 0 to N-1

if ARGV.length != 4
puts <<-HELPMESSAGE
Automate the construction of landscapes
Copyright (c) Peter Lane, 2008.

ruby create-samples.rb n m k N

where n = number of variables
m = number of clauses
k = number of variables per clause
N = number of formulae

Results for formulae constructed with names: results-n-m-k-i.txt
where i runs from 0 to N-1
HELPMESSAGE
else
n = ARGV[0].to_i
m = ARGV[1].to_i
k = ARGV[2].to_i
N = ARGV[3].to_i
N.times do |i|
puts "Creating file #{i} for n=#{n}, m=#{m}, k=#{k}"
`../makels #{n} #{m} #{k} > results-#{n}-#{m}-#{k}-#{i}.txt`
sleep 1.0 # wait at least a second, to allow for C's randomisation!
end
end