Forum: Ruby Can I run windows command line app interactively?

1ebc214732ace57a16fde62b4587b47f?d=identicon&s=25 Cai Inception (cai_i)
on 2010-11-04 09:52
I want to do something like this:

    # open a session
    @session = IO.popen('cmd.exe', 'r+')

    # run a command and process outputs
    @session.puts('SET ABC=123')
    @session.each do |line|
      p line
    end

    # run a command line application
    @session.puts('ECHO %ABC%')
    @session.each do |line|
      p line
    end

The issue is that the first "@session.each" never stop. It waits when
cmd.exe is waiting for input. So the scripts hang and my next
"@session.puts" stuff will never run.


Thanks!
C5e056d4888f81842e966ff308c03416?d=identicon&s=25 Jeremy Bopp (Guest)
on 2010-11-04 17:15
(Received via mailing list)
On 11/4/2010 3:52 AM, Cai I. wrote:
>
>     # run a command line application
>     @session.puts('robocopy.exe ......')
>     @session.each do |line|
>       p line
>     end
>
> The issue is that "@session.each" never stop. It waits when cmd.exe
> waiting for input. So the scripts hang.

The problem is that @session.each will run until the input stream is
closed, in this case until cmd.exe exits.  What you want to do is have
it run only until you get the next prompt waiting for input.  For that
you need to use something like the expect module:

require 'expect'
session = IO.popen('cmd.exe', 'r+')
session.puts('dir')
session.expect(/[CD]:\\.*>/) do
  session.puts('robocopy.exe ......')
end
session.expect(/[CD]:\\.*>/) do
  session.puts('exit')
end
session.close


The only problem with this solution is that you won't be able to see the
output of the commands you run this way.  That output is consumed by
#expect, and I don't know of a way to make it print what it consumes.
Of course another way to handle your exact example is something like
this:

system('dir && robocopy.exe .......')


As long as you don't intend to process the output of commands along the
way in order to change how you run the subsequent commands, this should
work for you.  If you *do* want to process the output then you could run
each command in a separate session and read the output in between each
session:

session = IO.popen('dir')
session.each do |line|
  puts line
end
session.close

session = IO.popen('robocopy.exe .....')
session.each do |line|
  puts line
end
session.close


-Jeremy
1ebc214732ace57a16fde62b4587b47f?d=identicon&s=25 Cai Inception (cai_i)
on 2010-11-10 19:37
Jeremy Bopp wrote in post #959348:
> On 11/4/2010 3:52 AM, Cai I. wrote:
>>
>>     # run a command line application
>>     @session.puts('robocopy.exe ......')
>>     @session.each do |line|
>>       p line
>>     end
>>
>> The issue is that "@session.each" never stop. It waits when cmd.exe
>> waiting for input. So the scripts hang.
>
> The problem is that @session.each will run until the input stream is
> closed, in this case until cmd.exe exits.  What you want to do is have
> it run only until you get the next prompt waiting for input.  For that
> you need to use something like the expect module:
>
> require 'expect'
> session = IO.popen('cmd.exe', 'r+')
> session.puts('dir')
> session.expect(/[CD]:\\.*>/) do
>   session.puts('robocopy.exe ......')
> end
> session.expect(/[CD]:\\.*>/) do
>   session.puts('exit')
> end
> session.close
>
>
> The only problem with this solution is that you won't be able to see the
> output of the commands you run this way.  That output is consumed by
> #expect, and I don't know of a way to make it print what it consumes.
> Of course another way to handle your exact example is something like
> this:
>
> system('dir && robocopy.exe .......')
>
>
> As long as you don't intend to process the output of commands along the
> way in order to change how you run the subsequent commands, this should
> work for you.  If you *do* want to process the output then you could run
> each command in a separate session and read the output in between each
> session:
>
> session = IO.popen('dir')
> session.each do |line|
>   puts line
> end
> session.close
>
> session = IO.popen('robocopy.exe .....')
> session.each do |line|
>   puts line
> end
> session.close
>
>
> -Jeremy

Thanks a lot Jeremy for reply.

I have to get the output for parsing in my scenario, and I finally got
it done by monitoring the output and stop read when the prompt found, I
guess it likes what the "expect" does. If no prompt found, I will parse
out each line and get it processed.

I found the function ios.readpartial() very useful in this scenario. I
use it to read output from cmd.exe. It won't get blocked at the end of
output as long as it has read something. I guess it just read what in
the system buffer and return and it only block when nothing left in
buffer to read.
B2b20140efe14944692ab82a08f9b1c7?d=identicon&s=25 Charles Calvert (Guest)
on 2010-11-10 19:56
(Received via mailing list)
On Thu, 4 Nov 2010 03:52:59 -0500, "Cai I." <cai.inception@gmail.com>
wrote in <ea0055cfedb122c2ed0424aaf5e406c0@ruby-forum.com>:

>
>    # run a command line application
>    @session.puts('robocopy.exe ......')
>    @session.each do |line|
>      p line
>    end

You can do this, but you'll need to use the Win32 API to attach to a
console (command shell) and be able to read and write to its STDIN and
STDOUT.  See the links below:

<http://www.dreamincode.net/code/snippet921.htm>
<http://www.halcyon.com/~ast/dload/guicon.htm>
<http://msdn.microsoft.com/en-us/library/ms681944%2...

The first two are links to examples, and the last is to the MSDN
documentation.  It's been a number of years since I did this, but it's
the technique that Visual Studio uses to run the command-line
compilers and pipe their output back into the IDE.
1ebc214732ace57a16fde62b4587b47f?d=identicon&s=25 Cai Inception (cai_i)
on 2010-11-13 04:50
Charles Calvert wrote in post #960600:
> You can do this, but you'll need to use the Win32 API to attach to a
> console (command shell) and be able to read and write to its STDIN and
> STDOUT.  See the links below:
>
> <http://www.dreamincode.net/code/snippet921.htm>
> <http://www.halcyon.com/~ast/dload/guicon.htm>
> <http://msdn.microsoft.com/en-us/library/ms681944%2...
>
> The first two are links to examples, and the last is to the MSDN
> documentation.  It's been a number of years since I did this, but it's
> the technique that Visual Studio uses to run the command-line
> compilers and pipe their output back into the IDE.

Thanks Charles, this looks promising.

One little question is whether the ruby program need to create another
process for the new console? the MSDN doc says "A process can be
associated with only one console, so the AllocConsole function fails if
the calling process already has a console. A process can use the
FreeConsole function to detach itself from its current console, then it
can call AllocConsole to create a new console or AttachConsole to attach
to another console.If the calling process creates a child process, the
child inherits the new console."

I am not familiar with calling Win AP in ruby, but I will surely take it
a try when I got time later.
B2b20140efe14944692ab82a08f9b1c7?d=identicon&s=25 Charles Calvert (Guest)
on 2010-11-13 16:46
(Received via mailing list)
On Fri, 12 Nov 2010 22:50:32 -0500, Cai Inception
<cai.inception@gmail.com> wrote in
<422bf4524ffd3e8cb2b513afdca90793@ruby-forum.com>:

>> documentation.  It's been a number of years since I did this, but it's
>> the technique that Visual Studio uses to run the command-line
>> compilers and pipe their output back into the IDE.
>
>Thanks Charles, this looks promising.

You're welcome.

>One little question is whether the ruby program need to create another
>process for the new console? the MSDN doc says "A process can be
>associated with only one console, so the AllocConsole function fails if
>the calling process already has a console. A process can use the
>FreeConsole function to detach itself from its current console, then it
>can call AllocConsole to create a new console or AttachConsole to attach
>to another console.If the calling process creates a child process, the
>child inherits the new console."

That's a good question.  I've always done this from GUI apps where the
use case was the same as Visual Studio's.  As a result, the
application didn't have a console allocated.

I guess it depends on what your use case is.  Are you running the Ruby
app via the MRI from a console, or is it a web app running via
Apache/Nginx/etc.?  In the latter case, I wouldn't think that you'd
have a console allocated to the app.  You could always call
AllocConsole as a test to see what happens.

If your case is the former, then Jeremy Boop's suggestion might be the
way to go.  When I wrote my original reply, I was thinking of a GUI or
webapp controlling the console app.
1ebc214732ace57a16fde62b4587b47f?d=identicon&s=25 Cai Inception (cai_i)
on 2010-11-17 02:12
Charles Calvert wrote in post #961204:
>
> If your case is the former, then Jeremy Boop's suggestion might be the
> way to go.  When I wrote my original reply, I was thinking of a GUI or
> webapp controlling the console app.

My case is the former. I write scripts running regular jobs and they
start in console with ruby.exe. So I will go wiht Jeremy Boop's
suggestion. But still thanks for replying and clarifying, Charles.
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.