I have always tried to be a polyglot programmer, and also poly-platform one.
I have always cherished the idea my softwares could run on any OS. My main development environments for most of my Ruby projects are Windows (XP and 7), *nix and Cygwin.
So far I always managed to implement cross-platforms solutions, even if they sometimes needed some dirty targeted if…then based on platforms or Ruby versions.
However today I ran into a wall.
I want to execute an external process, get its output in real time, and pilot it by writing to its input in the same time.
The goal is to mimic a real user’s behavior reading his screen and writing to his terminal in real time.
Simply put: I gave up on native Windows. Cygwin is then mandatory for this task 🙁
EDIT: I managed to find a solution: see my next blog post for details.
EDIT 2: I packaged the solution in a nice Ruby gem: ProcessPilot.
I first tried to use Ruby’s standard library or external gems to spawn the process and get access to its stdin and stdout.
After failing, I tried to do it manually, by forking and redirecting pipes between parent and child processes and piloting the child process from his parent. Still fails.
Then I thought about using files as redirections for stdout, stderr and stdin. This works perfectly for stdout and stderr, but no way for stdin. Even if the stdin file is empty, the process still mimics inputs (and I tested it even without Ruby – this is a Windows/cmd.exe behavior).
Here are the main points I considered:
- Open3::popen3, IO::popen, Process::spawn, Kernel::open, systemu all behave the same way: you can’t get output in real time. You get the whole output once the process exits.
- PTY::spawn is the perfect solution, but there is no implementation on Windows.
- Native Ruby on windows does not have a fork implementation.
- win32-process gem offers a fork implementation on Windows, but descriptors are broken by the fork. Therefore it is impossible to pilot the child process from its parent by redirecting its stdin and stdout to pipes.
- A Windows command cannot receive its stdin input from a streamed file. The file has to contain all the lines to input at the beginning of the process run.
If any of my readers have better clues, please share them for the sake of everyone!
Pingback: How to pilot external processes’ stdin and stdout in real time using Ruby | Muriel's Tech Blog