Due programmi con StdIn e StdOut collegati


12

Supponiamo che io abbia due programmi chiamati ProgramAe ProgramB. Voglio eseguirli entrambi contemporaneamente nell'interprete cmd di Windows. Ma voglio il StdOutdi ProgramAagganciato al StdIndi ProgramBe il StdOutdi ProgramBagganciato al StdIndi ProgramA.

Qualcosa come questo

 ________________ ________________
| | | |
| StdIn (== ← === ← == (StdOut |
| Programma A | | Programma B |
| | | |
| StdOut) == → === → ==) StdIn |
| ________________ | | ________________ |

C'è un comando per fare questo - un modo per ottenere questa funzionalità da cmd?


4
Su unix userei named pipe, per fare questo, Windows ha qualcosa chiamato named pipe completamente diverso e probabilmente non applicabile.
Jasen,

@Jasen secondo un commento qui linuxjournal.com/article/2156 named pipe potrebbe funzionare su cygwin .. in tal caso potresti forse elaborare e pubblicare una risposta, anche se vale la pena testare su cygwin prima
barlop

Suppongo che anche i programmi dovrebbero essere scritti in modo da non eseguire il ciclo. Quindi, se lo stdout è una riga vuota, non alimentare con lo stdin, e se nulla è lo stdin, uscire. Evitando così un ciclo infinito? Ma per interesse, qual è l'applicazione?
barlop

2
dire che vuoi avere due istanze di "Eliza" avere una conversazione?
Jasen,

Se i programmi non sono progettati con cura per gestirlo, potrebbero entrare in un deadlock - entrambi sono in attesa di input dall'altro e non succede mai nulla (o entrambi stanno cercando di scrivere su buffer completi e non leggono mai nulla per svuotarli )
Casuale 832,

Risposte:


5

Non solo può essere fatto, può essere fatto con nient'altro che un file batch! :-)

Il problema può essere risolto utilizzando un file temporaneo come "pipe". La comunicazione bidirezionale richiede due file "pipe".

Il processo A legge stdin da "pipe1" e scrive stdout su "pipe2" Il
processo B legge stdin da "pipe2" e scrive stdout su "pipe1"

È importante che entrambi i file esistano prima dell'avvio di entrambi i processi. I file dovrebbero essere vuoti all'inizio.

Se un file batch tenta di leggere da un file che si trova alla fine corrente, non restituisce semplicemente nulla e il file rimane aperto. Quindi la mia routine readLine legge continuamente fino a quando non ottiene un valore non vuoto.

Voglio essere in grado di leggere e scrivere una stringa vuota, quindi la mia routine writeLine aggiunge un carattere in più che readLine rimuove.

Il mio processo A controlla il flusso. Inizia le cose scrivendo 1 (messaggio a B), quindi entra in un ciclo con 10 iterazioni in cui legge un valore (messaggio da B), aggiunge 1 e quindi scrive il risultato (messaggio a B). Alla fine attende l'ultimo messaggio da B, quindi scrive un messaggio "Esci" su B ed esce.

Il mio processo B è in un ciclo condizionatamente infinito che legge un valore (messaggio da A), aggiunge 10 e quindi scrive il risultato (messaggio su A). Se B legge mai un messaggio "Esci", termina immediatamente.

Volevo dimostrare che la comunicazione è completamente sincrona, quindi introduco un ritardo nei cicli di processo A e B.

Si noti che la procedura readLine è in un ciclo stretto che abusa continuamente sia della CPU che del file system mentre attende l'input. È possibile aggiungere un ritardo PING al loop, ma i processi non saranno così reattivi.

Uso una vera pipa per comodità sia per avviare i processi A che B. Ma la pipe non è funzionale in quanto nessuna comunicazione vi passa attraverso. Tutte le comunicazioni avvengono tramite i miei file temporanei "pipe".

Avrei potuto anche usare START / B per avviare i processi, ma poi devo rilevare quando entrambi terminano in modo da sapere quando eliminare i file temporanei "pipe". È molto più semplice usare la pipa.

Ho scelto di mettere tutto il codice in un singolo file: lo script principale che avvia A e B, così come il codice per A e B. Avrei potuto usare un file di script separato per ogni processo.

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

--PRODUZIONE--

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 vita è un po 'più semplice con un linguaggio di livello superiore. Di seguito è riportato un esempio che utilizza VBScript per i processi A e B. Uso ancora batch per avviare i processi. Uso un metodo molto interessante descritto in È possibile incorporare ed eseguire VBScript in un file batch senza utilizzare un file temporaneo? per incorporare più script VBS in un singolo script batch.

Con un linguaggio superiore come VBS, possiamo usare una normale pipe per passare informazioni da A a B. Abbiamo solo bisogno di un singolo file "pipe" temporaneo per passare informazioni da B a A. Perché ora abbiamo una pipe funzionante, la A Non è necessario che il processo invii un messaggio "esci" a B. Il processo B esegue semplicemente un ciclo fino a quando non raggiunge la fine del file.

È bello avere accesso a una corretta funzione sleep in VBS. Ciò mi consente di introdurre facilmente un breve ritardo nella funzione readLine per interrompere la CPU.

Tuttavia, c'è una ruga in readLIne. All'inizio stavo ricevendo errori intermittenti fino a quando mi sono reso conto che a volte readLine avrebbe rilevato informazioni disponibili su stdin e avrebbe immediatamente provato a leggere la riga prima che B avesse la possibilità di terminare di scrivere la riga. Ho risolto il problema introducendo un breve ritardo tra il test di fine file e la lettura. Un ritardo di 5 msec sembrava fare il trucco per me, ma l'ho raddoppiato a 10 msec solo per essere al sicuro. È molto interessante che il batch non soffra di questo problema. Ne abbiamo discusso brevemente (5 brevi post) su 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>

L'output è lo stesso della soluzione batch pura, tranne che le righe finali "quit" non sono presenti.


4

Nota: a posteriori, leggendo di nuovo la domanda, questo non fa ciò che viene chiesto. Dal momento che, mentre collega due processi insieme (in un modo interessante che funzionerebbe anche su una rete!), Non collega entrambi i modi.


Spero che tu abbia qualche risposta a questo.

Ecco la mia risposta ma non accettarla, attendi altre risposte, sono curioso di vedere altre risposte.

Questo è stato fatto da Cygwin. E usando il comando 'nc' (quello intelligente). 'Wc -l' conta solo le righe. Quindi sto collegando qualunque due comandi, in questo caso, echo e wc, usando nc.

Il comando a sinistra è stato eseguito per primo.

nc è un comando in grado di a) creare un server oppure b) connettersi come il comando telnet in modalità raw a un server. Sto usando l'uso 'a' nel comando a sinistra e l'uso 'b' nel comando a destra.

Quindi nc si è seduto lì ad ascoltare un input, quindi avrebbe reindirizzato quell'input wc -le conteggiato le linee e prodotto il numero di righe immesse.

Ho quindi eseguito la riga per riecheggiare un po 'di testo e inviarlo grezzo a 127.0.0.1:123 che è il server menzionato.

inserisci qui la descrizione dell'immagine

È possibile copiare il comando nc.exe da cygwin e provare a utilizzare quello e il file cygwin1.dll necessari nella stessa directory. Oppure potresti farlo da Cygwin stesso come ho fatto io. Non vedo nc.exe in gnuwin32. Hanno una ricerca http://gnuwin32.sourceforge.net/ e nc o netcat non vengono visualizzati. Ma puoi ottenere cygwin https://cygwin.com/install.html


Mi ci sono volute circa dieci volte per leggerlo prima di capire cosa stesse succedendo ..... è piuttosto intelligente
DarthRubik,

@DarthRubik sì, e puoi usare netstat -aon | find ":123"per vedere che il comando a sinistra ha creato il server
barlop

Ma come si comunica nell'altra direzione (es. Da wcdietro al echocomando).
DarthRubik,

quando provo con nc a farlo in entrambi i modi non riesco a farlo funzionare, forse nc entra in un ciclo di qualche tipo.
barlop

nc -lp9999 | prog1 | prog2 | nc 127.0.0.1 9999potrebbe essere necessario adottare delle misure per garantire che i buffer vengano scaricati in modo tempestivo
Jasen

2

Un trucco (preferirei non farlo, ma questo è quello per cui sto andando per ora) è quello di scrivere un'applicazione C # per farlo per te. Non ho implementato alcune funzionalità chiave di questo programma (come usare effettivamente gli argomenti che mi sono stati forniti), ma eccolo qui:

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();

        }
    }
}

Quindi alla fine, quando questo programma è completamente funzionante e il codice di debug viene rimosso, lo useresti in questo modo:

joiner "A A's args" "B B's Args"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.