Damn you cmd.exe!

Discussion in 'Ruby' started by Adam Skobi, Oct 16, 2008.

  1. Adam Skobi

    Adam Skobi Guest

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

    --
    Adam Skobi
     
    Adam Skobi, Oct 16, 2008
    #1
    1. Advertising

  2. Adam Skobi

    Mike Gold Guest

    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?
    --
    Posted via http://www.ruby-forum.com/.
     
    Mike Gold, Oct 17, 2008
    #2
    1. Advertising

  3. Adam Skobi

    Roger Pack Guest

    > 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.
    --
    Posted via http://www.ruby-forum.com/.
     
    Roger Pack, Oct 17, 2008
    #3
  4. Adam Skobi

    Adam Skobi Guest

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

    --
    Adam Skobi
     
    Adam Skobi, Oct 17, 2008
    #4
  5. 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.

    > 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
     
    Robert Klemme, Oct 17, 2008
    #5
  6. Adam Skobi

    Adam Skobi Guest

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

    On Fri, Oct 17, 2008 at 10:39 AM, Robert Klemme
    <>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.


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


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



    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.

    --
    Adam Skobi
     
    Adam Skobi, Oct 17, 2008
    #6
  7. 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.

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

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

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


    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
     
    Robert Klemme, Oct 17, 2008
    #7
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Achim Domma (Procoders)

    read input for cmd.Cmd from file

    Achim Domma (Procoders), Jun 3, 2005, in forum: Python
    Replies:
    2
    Views:
    8,131
    Peter Otten
    Jun 3, 2005
  2. Sarir Khamsi

    Interpreter-like help in cmd.Cmd

    Sarir Khamsi, Jun 9, 2005, in forum: Python
    Replies:
    4
    Views:
    397
    Bengt Richter
    Jun 26, 2005
  3. =?ISO-8859-1?Q?Sch=FCle_Daniel?=

    [exec cmd for cmd in cmds]

    =?ISO-8859-1?Q?Sch=FCle_Daniel?=, Mar 8, 2006, in forum: Python
    Replies:
    3
    Views:
    415
    Scott David Daniels
    Mar 8, 2006
  4. Diez B. Roggisch

    pydb remote debugging/cmd.Cmd over socket?

    Diez B. Roggisch, May 28, 2008, in forum: Python
    Replies:
    2
    Views:
    576
    Diez B. Roggisch
    May 29, 2008
  5. Sol Linderstein
    Replies:
    3
    Views:
    216
    Matt Garrish
    Jun 18, 2004
Loading...

Share This Page