Calling a subprocess with specific arguments and capturing its output?

Hi. I haven’t written Ruby in a while, and I was wondering if someone
could help with a problem I’ve never managed to solve. I’m writing a
shell script that takes filenames as its arguments, and calls “du” to
get their size. Because I don’t want to have to escape everything
perfectly, I was looking for a function with a syntax that allows
separate arguments, like system(“du”, “-sh”, filename).

system() doesn’t allow me to capture the output of the subprocess,
while popen , ``, and %x() don’t let me specify a list of discrete
arguments. Is there an elegant solution to this problem? My current
solution is to just escape the arguments and put them into a string,
but this is ugly and buggy.

Thanks in advance!
-Dan

On Nov 10, 6:00 pm, Dan Q [email protected] wrote:

Hi. I haven’t written Ruby in a while, and I was wondering if someone
could help with a problem I’ve never managed to solve. I’m writing a
shell script that takes filenames as its arguments, and calls “du” to
get their size. Because I don’t want to have to escape everything
perfectly, I was looking for a function with a syntax that allows
separate arguments, like system(“du”, “-sh”, filename).

def backtick(cmd,*args)
IO.popen(‘-’) {|f| f ? f.read : exec(cmd,*args)}
end

backtick ‘du’, ‘-sh’, filename

From: “Dan Q” [email protected]

arguments. Is there an elegant solution to this problem? My current
solution is to just escape the arguments and put them into a string,
but this is ugly and buggy.

One possibility might be to let Shellwords handle the
escaping:

irb(main):001:0> require ‘shellwords’
irb(main):002:0> args = [“foo”, “bar baz”, “mary ‘had’ a”, ‘little
“lamb” and stuff’]
irb(main):003:0> puts( args.map {|a| a.shellescape}.join(" ") )
foo bar\ baz mary\ 'had'\ a little\ "lamb"\ and\ stuff

Alternately, if you can use ruby 1.9.x, there appears to be
a very powerful new Kernel.spawn command. (See the ri
documentation, several pages…)

Also in 1.9.x, it appears to now be possible to pass an
Array for the command arguments to popen:

irb(main):001:0> IO.popen([“ls”, “-l”, “GL_GameOfLife.zip”,
“quake3-1.32b-source.zip”], “r”) {|io| puts(io.read)}
-rw------- 1 billk billk 3540592 Feb 23 2009 GL_GameOfLife.zip
-rw------- 1 billk billk 5724791 Sep 21 2005 quake3-1.32b-source.zip

Hope this helps,

Bill

def backtick(cmd,*args)
IO.popen(’-’) {|f| f ? f.read : exec(cmd,*args)}
end

backtick ‘du’, ‘-sh’, filename

Thanks! That’s a very nice solution, and more concise than I would
have thought possible. I’ll use this until ruby-1.9 or 2.0 becomes
standard.

-Dan

On Tue, Nov 10, 2009 at 5:43 PM, Bill K. [email protected] wrote:

Alternately, if you can use ruby 1.9.x, there appears to be
a very powerful new Kernel.spawn command. (See the ri
documentation, several pages…)

Also in 1.9.x, it appears to now be possible to pass an Array for the
command arguments to popen:

irb(main):001:0> IO.popen([“ls”, “-l”, “GL_GameOfLife.zip”,
“quake3-1.32b-source.zip”], “r”) {|io| puts(io.read)}

Thanks, Bill. I’m glad to hear that the standard library will be
getting the capability to do this without a hack, because it seems
like a big omission in ruby-1.8.

-Dan

On Tue, Nov 10, 2009 at 9:48 PM, Tony A. [email protected] wrote:

On Tue, Nov 10, 2009 at 4:00 PM, Dan Q [email protected] wrote:

I was looking for a function with a syntax that allows
separate arguments, like system(“du”, “-sh”, filename)

Why is this any harder than?

#{cmd} #{args.map { |arg| arg.to_s.inspect }.join(' ')}

That comes a lot closer to working than I had expected :wink:
I’d rather not deal with escaping, at all. Won’t shell commands in
backticks be run differently, depending on what the system shell is?
I’m not sure whether all unix shells have similar escaping syntax.

The escaping done by inspect() seems to fail when the filename begins
with whitespace or contains a newline.

-Dan

On Tue, Nov 10, 2009 at 4:00 PM, Dan Q [email protected] wrote:

I was looking for a function with a syntax that allows
separate arguments, like system(“du”, “-sh”, filename)

Why is this any harder than?

#{cmd} #{args.map { |arg| arg.to_s.inspect }.join(' ')}

2009/11/11 Dan Q [email protected]:

That comes a lot closer to working than I had expected :wink:
I’d rather not deal with escaping, at all. Won’t shell commands in
backticks be run differently, depending on what the system shell is?
I’m not sure whether all unix shells have similar escaping syntax.

The escaping done by inspect() seems to fail when the filename begins
with whitespace or contains a newline.

If you are on 1.9, escaping is superfluous: you can pass an Array of
arguments directly:

irb(main):001:0> IO.popen(%w{echo *}, “r”) {|io| puts io.read}
*
=> nil
irb(main):002:0> IO.popen([“ls”, “-a”, “-F”], “r”) {|io| puts io.read}
./
…/
=> nil

This is a lot safer, significantly less error prone and is probably
more efficient as well (because there is no shell needed for parsing
the command line).

As far as I can see this does not work in 1.8.7.

Kind regards

robert

On Nov 11, 2009, at 2:45 AM, Dan Q wrote:

Won’t shell commands in backticks be run differently, depending on what the system shell is? I’m not sure whether all unix shells have similar escaping syntax.

I don’t believe so. I think backticks will default to the lowest common
denominator of sh.

James Edward G. II