Dos programas que tienen su StdIn y StdOut atados

12

Supongamos que tengo dos programas llamados ProgramAy ProgramB. Quiero ejecutarlos simultáneamente en el intérprete cmd de Windows. Pero quiero lo StdOutde ProgramAenganchado a lo StdInde ProgramBy lo StdOutde ProgramBenganchado a lo StdInde ProgramA.

Algo como esto

 ________________ ________________
El | El | El | El |
El | StdIn (== ← === ← == (StdOut |
El | Programa A | El | Programa B |
El | El | El | El |
El | StdOut) == → === → ==) StdIn |
| ________________ | | ________________ |

¿Hay algún comando para hacer esto? ¿Alguna forma de lograr esta funcionalidad desde cmd?

DarthRubik
fuente
44
En unix, usaría tuberías con nombre, para hacer esto, Windows tiene algo llamado tuberías con nombre que es completamente diferente y posiblemente no aplicable.
Jasen
@Jasen de acuerdo con un comentario aquí linuxjournal.com/article/2156 las tuberías con nombre podrían funcionar en cygwin ... si es así, tal vez podría elaborar y publicar como respuesta, aunque vale la pena probar en cygwin primero
barlop
Supongo que los programas también tendrían que escribirse para que no se repitan. Entonces, si el stdout es una línea vacía, no alimente a stdin, y si nada es stdin, salga. ¿Así evitando un bucle infinito? Pero fuera de interés, ¿cuál es la aplicación?
barlop
2
¿Dices que quieres tener dos instancias de "Eliza" para tener una conversación?
Jasen
Si los programas no están cuidadosamente diseñados para manejar esto, podrían quedar en un punto muerto: ambos esperan la entrada del otro y nunca sucede nada (o ambos intentan escribir en buffers completos y nunca leen nada para vaciarlos) )
Random832

Respuestas:

5

¡No solo se puede hacer, se puede hacer con nada más que un archivo por lotes! :-)

El problema se puede resolver mediante el uso de un archivo temporal como "tubería". La comunicación bidireccional requiere dos archivos "pipe".

El proceso A lee stdin de "pipe1" y escribe stdout en "pipe2" El
proceso B lee stdin de "pipe2" y escribe stdout en "pipe1"

Es importante que ambos archivos existan antes de iniciar cualquiera de los procesos. Los archivos deben estar vacíos al inicio.

Si un archivo por lotes intenta leer de un archivo que se encuentra en el extremo actual, simplemente no devuelve nada y el archivo permanece abierto. Entonces, mi rutina readLine se lee continuamente hasta que obtiene un valor no vacío.

Quiero poder leer y escribir una cadena vacía, por lo que mi rutina writeLine agrega un carácter adicional que readLine elimina.

Mi proceso A controla el flujo. Inicia cosas escribiendo 1 (mensaje a B), y luego ingresa a un ciclo con 10 iteraciones donde lee un valor (mensaje de B), agrega 1 y luego escribe el resultado (mensaje a B). Finalmente, espera el último mensaje de B, y luego escribe un mensaje de "salir" a B y sale.

Mi proceso B está en un bucle condicionalmente interminable que lee un valor (mensaje de A), agrega 10 y luego escribe el resultado (mensaje en A). Si B alguna vez lee un mensaje de "salir", entonces termina inmediatamente.

Quería demostrar que la comunicación es totalmente sincrónica, por lo que introduzco un retraso en los bucles de proceso A y B.

Tenga en cuenta que el procedimiento readLine está en un circuito cerrado que abusa continuamente de la CPU y del sistema de archivos mientras espera la entrada. Se podría agregar un retraso PING al bucle, pero luego los procesos no serán tan receptivos.

Utilizo una tubería verdadera como una conveniencia para iniciar los procesos A y B. Pero la tubería no funciona porque no pasa comunicación por ella. Toda la comunicación es a través de mis archivos temporales "pipe".

También podría haber usado START / B para iniciar los procesos, pero luego tengo que detectar cuándo ambos terminan para saber cuándo eliminar los archivos temporales "pipe". Es mucho más simple usar la tubería.

Elegí poner todo el código en un solo archivo: el script maestro que inicia A y B, así como el código para A y B. Podría haber usado un archivo de script separado para cada proceso.

test.bat

@echo off

if "%~1" equ "" (

    copy nul pipe1.txt >nul
    copy nul pipe2.txt >nul

    "%~f0" A <pipe1.txt >>pipe2.txt | "%~f0" B <pipe2.txt >>pipe1.txt

    del pipe1.txt pipe2.txt

    exit /b

)


setlocal enableDelayedExpansion
set "prog=%~1"
goto !prog!


:A
call :writeLine 1
for /l %%N in (1 1 5) do (
  call :readLine
    set /a ln+=1
  call :delay 1
    call :writeLine !ln!
)
call :readLine
call :delay 1
call :writeLine quit
exit /b


:B
call :readLine
if !ln! equ quit exit /b
call :delay 1
set /a ln+=10
call :writeLine !ln!
goto :B


:readLine
set "ln="
set /p "ln="
if not defined ln goto :readLine
set "ln=!ln:~0,-1!"
>&2 echo !prog!  reads !ln!
exit /b


:writeLine
>&2 echo !prog! writes %*
echo(%*.
exit /b


:delay
setlocal
set /a cnt=%1+1
ping localhost /n %cnt% >nul
exit /b

--SALIDA--

C:\test>test
A writes 1
B  reads 1
B writes 11
A  reads 11
A writes 12
B  reads 12
B writes 22
A  reads 22
A writes 23
B  reads 23
B writes 33
A  reads 33
A writes 34
B  reads 34
B writes 44
A  reads 44
A writes 45
B  reads 45
B writes 55
A  reads 55
A writes 56
B  reads 56
B writes 66
A  reads 66
A writes quit
B  reads quit

La vida es un poco más fácil con un lenguaje de nivel superior. A continuación se muestra un ejemplo que usa VBScript para los procesos A y B. Todavía uso el lote para iniciar los procesos. Utilizo un método muy bueno descrito en ¿Es posible incrustar y ejecutar VBScript dentro de un archivo por lotes sin usar un archivo temporal? para incrustar múltiples scripts de VBS dentro de un solo script por lotes.

Con un lenguaje superior como VBS, podemos usar una tubería normal para pasar información de A a B. Solo necesitamos un único archivo "tubería" temporal para pasar información de B de nuevo a A. Como ahora tenemos una tubería funcional, la A el proceso no necesita enviar un mensaje de "salir" a B. El proceso B simplemente se repite hasta que llega al final del archivo.

Seguro que es bueno tener acceso a una función de suspensión adecuada en VBS. Esto me permite introducir fácilmente un pequeño retraso en la función readLine para dar un descanso a la CPU.

Sin embargo, hay una arruga dentro de readLIne. Al principio estaba teniendo fallas intermitentes hasta que me di cuenta de que a veces readLine detectaría que la información está disponible en stdin, e inmediatamente trataría de leer la línea antes de que B tuviera la oportunidad de terminar de escribirla. Resolví el problema introduciendo un pequeño retraso entre la prueba de fin de archivo y la lectura. Un retraso de 5 ms pareció hacerme el truco, pero lo dupliqué a 10 ms para estar seguro. Es muy interesante que el lote no sufra este problema. Discutimos esto brevemente (5 publicaciones cortas) en http://www.dostips.com/forum/viewtopic.php?f=3&t=7078#p47432 .

<!-- : Begin batch script
@echo off
copy nul pipe.txt >nul
cscript //nologo "%~f0?.wsf" //job:A <pipe.txt | cscript //nologo "%~f0?.wsf" //job:B >>pipe.txt
del pipe.txt
exit /b


----- Begin wsf script --->
<package>

<job id="A"><script language="VBS">

  dim ln, n, i
  writeLine 1
  for i=1 to 5
    ln = readLine
    WScript.Sleep 1000
    writeLine CInt(ln)+1
  next
  ln = readLine

  function readLine
    do
      if not WScript.stdin.AtEndOfStream then
        WScript.Sleep 10 ' Pause a bit to let B finish writing the line
        readLine = WScript.stdin.ReadLine
        WScript.stderr.WriteLine "A  reads " & readLine
        exit function
      end if
      WScript.Sleep 10 ' This pause is to give the CPU a break
    loop
  end function

  sub writeLine( msg )
    WScript.stderr.WriteLine "A writes " & msg
    WScript.stdout.WriteLine msg
  end sub

</script></job>

<job id="B"> <script language="VBS">

  dim ln, n
  do while not WScript.stdin.AtEndOfStream
    ln = WScript.stdin.ReadLine
    WScript.stderr.WriteLine "B  reads " & ln
    n = CInt(ln)+10
    WScript.Sleep 1000
    WScript.stderr.WriteLine "B writes " & n
    WScript.stdout.WriteLine n
  loop

</script></job>

</package>

La salida es la misma que con la solución de lote puro, excepto que las líneas finales de "dejar de fumar" no están allí.

dbenham
fuente
4

Nota: en retrospectiva, al leer la pregunta nuevamente, esto no hace lo que se le pide. Dado que, si bien vincula dos procesos juntos (¡de una manera interesante que incluso funcionaría en una red!), No lo hace en ambos sentidos.


Espero que obtengas algunas respuestas a esto.

Aquí está mi respuesta, pero no la acepte, espere otras respuestas, estoy ansioso por ver otras respuestas.

Esto fue hecho desde cygwin. Y usando el comando 'nc' (el inteligente). El 'wc -l' solo cuenta líneas. Así que estoy vinculando cualesquiera dos comandos, en este caso, echo y wc, usando nc.

El comando de la izquierda se realizó primero.

nc es un comando que puede a) crear un servidor, o b) conectarse como el comando telnet en modo sin formato, a un servidor. Estoy usando el uso 'a' en el comando izquierdo y el uso 'b' en el comando derecho.

Así que nc se sentó allí escuchando a la espera de una entrada, y luego canalizó esa entrada wc -ly contó las líneas y emitió el número de líneas ingresadas.

Luego ejecuté la línea para hacer eco de un texto y enviarlo sin formato a 127.0.0.1:123, que es el servidor mencionado.

ingrese la descripción de la imagen aquí

Puede copiar el comando nc.exe de cygwin e intentar usar eso y el archivo cygwin1.dll que necesita en el mismo directorio. O podrías hacerlo desde el mismo Cygwin como yo. No veo nc.exe en gnuwin32. Tienen una búsqueda en http://gnuwin32.sourceforge.net/ y nc o netcat no aparecen. Pero puedes obtener cygwin https://cygwin.com/install.html

barlop
fuente
Me tomó como diez veces leer esto antes de entender lo que estaba pasando ... eso es bastante inteligente
DarthRubik
@DarthRubik sí, y puedes netstat -aon | find ":123"ver que el comando de la izquierda creó el servidor
barlop
Pero, ¿cómo se comunica la otra dirección (es decir, desde la parte wcposterior al echocomando)
DarthRubik
cuando intento con nc hacerlo de ambas maneras no puedo hacer que funcione, tal vez nc entra en un ciclo de algún tipo.
barlop
nc -lp9999 | prog1 | prog2 | nc 127.0.0.1 9999puede ser necesario tomar medidas para garantizar que los buffers se vacíen de manera oportuna
Jasen
2

Un truco (preferiría no hacer esto, pero esto es lo que voy a hacer por ahora) es escribir una aplicación C # para hacer esto por usted. No he implementado algunas características clave en este programa (como usar los argumentos que se me dieron), pero aquí está:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;


namespace Joiner
{
    class Program
    {
        static Process A;
        static Process B;
        static void AOutputted(object s, DataReceivedEventArgs a)
        {
            Console.WriteLine("A:" + a.Data);
            //Console.WriteLine("Sending to B");
            B.StandardInput.WriteLine(a.Data);
        }
        static void BOutputted(object s, DataReceivedEventArgs a)
        {
            Console.WriteLine("B:" + a.Data);
            //Console.WriteLine("Sending to A");
            A.StandardInput.WriteLine(a.Data);
        }
        static void Main(string[] args)
        {

            A = new Process();
            B = new Process();
            A.StartInfo.FileName = "help";
            B.StartInfo.FileName = "C:\\Users\\Owner\\Documents\\Visual Studio 2010\\Projects\\Joiner\\Test\\bin\\Debug\\Test.exe";

            A.StartInfo.Arguments = "mkdir";
            //B.StartInfo.Arguments = "/E /K type CON";

            A.StartInfo.UseShellExecute = false;
            B.StartInfo.UseShellExecute = false;

            A.StartInfo.RedirectStandardOutput = true;
            B.StartInfo.RedirectStandardOutput = true;

            A.StartInfo.RedirectStandardInput = true;
            B.StartInfo.RedirectStandardInput = true;

            A.OutputDataReceived += AOutputted;
            B.OutputDataReceived += BOutputted;

            A.Start();
            B.Start();

            A.BeginOutputReadLine();
            B.BeginOutputReadLine();



            while (!A.HasExited || !B.HasExited) { }
            Console.ReadLine();

        }
    }
}

Luego, eventualmente, cuando este programa sea completamente funcional y se elimine el código de depuración, lo usaría de la siguiente manera:

joiner "A A's args" "B B's Args"
DarthRubik
fuente