Forum: Ruby Damn you cmd.exe!

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
329f6728226e139a7b0b3b124e9b7044?d=identicon&s=25 Adam Skobi (Guest)
on 2008-10-17 00:40
(Received via mailing list)
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.
A246f7c0ce5f2909483d358bd9e83e4e?d=identicon&s=25 Mike Gold (mikegold)
on 2008-10-17 04:11
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?
Bec38d63650c8912b6ba9b557fb953b9?d=identicon&s=25 Roger Pack (rogerdpack)
on 2008-10-17 08:14
> 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.
329f6728226e139a7b0b3b124e9b7044?d=identicon&s=25 Adam Skobi (Guest)
on 2008-10-17 09:28
(Received via mailing list)
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.
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2008-10-17 10:45
(Received via mailing list)
On 17.10.2008 00:39, Adam Skobi wrote:
> 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.

> 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
329f6728226e139a7b0b3b124e9b7044?d=identicon&s=25 Adam Skobi (Guest)
on 2008-10-17 11:32
(Received via mailing list)
On Fri, Oct 17, 2008 at 10:39 AM, Robert Klemme
<shortcutter@googlemail.com>wrote:

>
>
>  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?
>

Native Windows Ruby.


> 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"


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


>> dir
>
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.
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2008-10-17 11:50
(Received via mailing list)
On 17.10.2008 11:29, Adam Skobi wrote:
> On Fri, Oct 17, 2008 at 10:39 AM, 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"
=> #<Thread:0x7ffa6fc0 dead>
irb(main):017:0>

> 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
This topic is locked and can not be replied to.