Damn you cmd.exe!

A

Adam Skobi

[Note: parts of this message were removed to make it a legal post.]

Hi,

As an exercise in metaprogramming I decided to write a DSL shell. One of the
requirements was that it should be working on both - win32 and unix
platforms. I quickly encountered some problems with cmd.exe.

My first understanding was that I open a stream to cmd.exe and write/read it
synchronously. In linux it would look like this

IO.popen( "/bin/bash", "r+" ) do |cmd|
cmd.puts 'pwd'
puts cmd.gets
end

Everything's dandy. It works like a charm. When I try to create an
equivalent in win32:

IO.popen( "cmd.exe", "r+" ) do |cmd|
cmd.puts 'cd'
puts cmd.gets
end

This thing fails miserably. I can't really pinpoint what is the root cause
of it. I have found here on comp.lang.ruby two solutions. First one:

IO.popen( "cmd.exe", "r+" ) do |cmd|
Thread.new(cmd) do |io|
while line = io.gets
puts line
end
end

cmd.puts "dir"
cmd.puts "cd \\"
cmd.puts "dir"

sleep 3
end

It works but is sucks badly on many levels as you can see. Other one:

IO.popen("cmd.exe /c #{single_command}") do | pipe |
pipe.each_line { |line| puts line }
end

This thing is not so bad but there is a small problem. I want my DSL user to
be able to write such simple commands as

cd C:\
dir

The problem is although the both command will run correctly, the change of
current directory will be lost between the commands. I can think of some
crazy workarounds but I was wondering if there is a way for the cmd.exe to
behave normally. Any help would be appreciated.
 
M

Mike Gold

cmd.exe would seem like a red herring here. Subprocesses do not modify
the environment of their parents, whether it's a cmd.exe process or
whether you've executed 'cd' directly without the shell.

The case is the same on Linux and Windows. Even if Windows had a cd.exe
executable (you could use cygwin's cd), it wouldn't help you.

Your purpose is unclear to me. If you want to make your own shell, you
read the "cd" string and translate it to Dir.chdir. If you want to run
a persistent cmd.exe, then your program is the one-liner
exec("cmd.exe"). Do you somehow seek something in between?
 
R

Roger Pack

As an exercise in metaprogramming I decided to write a DSL shell. One of
the
requirements was that it should be working on both - win32 and unix
platforms. I quickly encountered some problems with cmd.exe.

Note that EventMachine has a popen that might be useful.
 
A

Adam Skobi

[Note: parts of this message were removed to make it a legal post.]

Sorry for not beeing clear enough.
I want to have a consistent way of putting commands into the shell of both
platforms - win32 and linux. The linux solution that I posted seems natural
- open the /bin/bash and than operate by puts and gets. I was wondering if
it's possible to do the exact same thing with the cmd.exe i.e. without
opening the cmd.exe for each command (cmd.exe /C #{one_command_at_a_time})

The exec("cmd.exe") is not a solution as I want to have control of the input
and output.
 
R

Robert Klemme

As an exercise in metaprogramming I decided to write a DSL shell. One of the
requirements was that it should be working on both - win32 and unix
platforms. I quickly encountered some problems with cmd.exe.

My first understanding was that I open a stream to cmd.exe and write/read it
synchronously.

You probably rather want to read asynchronously though because
synchronously does not work properly under all conditions (namely if
more data is sent back and forth than the operating system's pipe buffer
size can handle, typically 4k AFAIK). You might rather want to try this:

require 'open3'

Open3.open3 "your command" do |stdin, stdout, stderr|
threads = [
Thread.new { stdout.each {|line| puts line} },
Thread.new { stderr.each {|line| puts line} },
]

begin
...

stdin.puts "exit"
ensure
threads.each {|th| th.join}
end
end
Everything's dandy. It works like a charm. When I try to create an
equivalent in win32:

Do you use the native Windows Ruby or cygwin?
IO.popen( "cmd.exe", "r+" ) do |cmd|
cmd.puts 'cd'
puts cmd.gets
end

This thing fails miserably. I can't really pinpoint what is the root cause
of it.

Then what does "fails miserably" mean? Any error messages?
I have found here on comp.lang.ruby two solutions. First one:

IO.popen( "cmd.exe", "r+" ) do |cmd|
Thread.new(cmd) do |io|
while line = io.gets
puts line
end
end

Yep, that's better anyway (see above).
cmd.puts "dir"
cmd.puts "cd \\"
cmd.puts "dir"

sleep 3
end

It works but is sucks badly on many levels as you can see. Other one:

You need to remember the thread and join it at the end of the block if
you want to make sure that all output from the shell is printed before
you leave the block.
IO.popen("cmd.exe /c #{single_command}") do | pipe |
pipe.each_line { |line| puts line }
end

This thing is not so bad but there is a small problem. I want my DSL user to
be able to write such simple commands as

cd C:\
dir

The problem is although the both command will run correctly, the change of
current directory will be lost between the commands. I can think of some
crazy workarounds but I was wondering if there is a way for the cmd.exe to
behave normally. Any help would be appreciated.

Well, what is "normal" anyway? Not all operating systems are similar
and just because Window's shell behaves differently does not make it
abnormal. :)

Kind regards

robert
 
A

Adam Skobi

[Note: parts of this message were removed to make it a legal post.]

Everything's dandy. It works like a charm. When I try to create an

Do you use the native Windows Ruby or cygwin?

Native Windows Ruby.

IO.popen( "cmd.exe", "r+" ) do |cmd|

Then what does "fails miserably" mean? Any error messages?

Yes i do. It writes in my native language but it translates to something
along the lines: "The process tried to write to a non-existent stream"

I have found here on comp.lang.ruby two solutions. First one:

Yep, that's better anyway (see above).

cmd.puts "dir"

You need to remember the thread and join it at the end of the block if you
want to make sure that all output from the shell is printed before you leave
the block.



IO.popen("cmd.exe /c #{single_command}") do | pipe |

Well, what is "normal" anyway? Not all operating systems are similar and
just because Window's shell behaves differently does not make it abnormal.
:)


You see, speaking simply, I want to have control on what 'goes in' and what
'goes out'. I don't want to have a background thread reading everything from
cmd.exe as I wont be able to tell which command produced the output. I
haven't tested it thoroughly but I think it should look like in /bin/bash.
Simple stuff:

shell.puts 'cd /home/me'
shell.puts 'pwd'
puts shell.gets # /home/me

I am really bedazzled as to why it doesn't seem to work in win32.
 
R

Robert Klemme

Native Windows Ruby.

There are some quirkynesses with that implementation which I'm not
familiar with. I use cygwin and it has served me well.
Yes i do. It writes in my native language but it translates to something
along the lines: "The process tried to write to a non-existent stream"

This works from cygwin:

irb(main):012:0> IO.popen "cmd.exe", "r+" do |io|
irb(main):013:1* t = Thread.new {io.each {|line| p line}}
irb(main):014:1> io.puts "dir /w", "exit"
irb(main):015:1> t.join
irb(main):016:1> end
"Microsoft Windows XP [Version 5.1.2600]\r\n"
"(C) Copyright 1985-2001 Microsoft Corp.\r\n"
"\r\n"
"C:\\cygwin\\home\\Robert>dir /w\n"
" Volume in Laufwerk C: hat keine Bezeichnung.\r\n"
" Volumeseriennummer: 5C71-9E29\r\n"
"\r\n"
" Verzeichnis von C:\\cygwin\\home\\Robert\r\n"
"\r\n"
"[.] [..] .bashrc .bash_aliases
..bash_history\r\n"
".bash_profile .inputrc .irbrc [bin] x.xml\r\n"
" 7 Datei(en) 10.364 Bytes\r\n"
" 3 Verzeichnis(se), 57.749.262.336 Bytes frei\r\n"
"\r\n"
"C:\\cygwin\\home\\Robert>exit\n"
You see, speaking simply, I want to have control on what 'goes in' and what
'goes out'. I don't want to have a background thread reading everything from
cmd.exe as I wont be able to tell which command produced the output. I
haven't tested it thoroughly but I think it should look like in /bin/bash.
Simple stuff:

shell.puts 'cd /home/me'
shell.puts 'pwd'
puts shell.gets # /home/me

I am really bedazzled as to why it doesn't seem to work in win32.

This solution is flawed as I have tried to explain earlier. Note that
you will create deadlocks if you do not read the output stream properly
(i.e. asynchronously).

If you want to make sure you know which command produced which output
you need to nevertheless read in the background and parse output for the
prompt. Then you know when sub process output has finished and can
attribute the output to commands properly. You also need proper
synchronization for this as well.

Note that you can also use File#expect.

Insisting that the world should be different won't help because that has
little effect on cmd.exe's implementation. You'll have to work with
what you got.

robert
 

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