Can I run windows command line app interactively?

C

Cai I.

I want to do something like this:

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

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

# 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.

Thanks!
 
J

Jeremy Bopp

I want to do something like this:

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

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

# 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
 
C

Cai I.

Jeremy Bopp wrote in post #959348:
# 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.
 
C

Charles Calvert

I want to do something like this:

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

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

# 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(VS.85).aspx>

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.
 
C

Cai Inception

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(VS.85).aspx>

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.
 
C

Charles Calvert

Charles Calvert wrote in post #960600:

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.
 
C

Cai Inception

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top