Zwei Programme, deren StdIn und StdOut gebunden sind

1612
DarthRubik

Angenommen, ich habe zwei Programme aufgerufen ProgramAund ProgramB. Ich möchte beide gleichzeitig im Windows-Cmd-Interpreter ausführen. Aber ich möchte, dass die StdOutvon ProgramAsüchtig die StdInvon ProgramBund die StdOutvon ProgramBgehakt auf die StdInvon ProgramA.

Etwas wie das

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

Gibt es einen Befehl dazu - einen Weg, um diese Funktionalität von cmd zu erreichen?

11
Unter Unix würde ich Named Pipes verwenden. Dazu hat Windows etwas Named Pipes, das völlig anders ist und möglicherweise nicht anwendbar ist. Jasen vor 8 Jahren 4
@Jasen Laut einem Kommentar hier http://www.linuxjournal.com/article/2156 können Named Pipes auf cygwin funktionieren. Wenn ja, dann könnten Sie vielleicht eine Antwort darauf erstellen und posten, obwohl es sich lohnt, zuerst auf cygwin zu testen barlop vor 8 Jahren 0
Ich nehme an, die Programme müssten auch so geschrieben werden, dass sie nicht in einer Schleife ablaufen. Wenn also stdout eine leere Zeile ist, füttern Sie nicht stdin, und wenn nichts zu stdin ist, beenden Sie das Programm. So vermeiden Sie eine Endlosschleife? Aber aus Interesse, was ist die Anwendung? barlop vor 8 Jahren 0
sagen, Sie möchten zwei Instanzen von "Eliza" führen, um sich zu unterhalten? Jasen vor 8 Jahren 2
Wenn die Programme nicht sorgfältig darauf ausgelegt werden, geraten sie möglicherweise in eine Sackgasse - beide warten auf Eingaben von den anderen und es passiert nie etwas (oder beide versuchen, in volle Puffer zu schreiben und lesen nie etwas, um sie zu leeren ) Random832 vor 8 Jahren 0
So etwas wie `tail -f bufferfile | ProgramA | ProgramB >> bufferfile` könnte funktionieren, aber a) Ich glaube, Windows hat kein "Tail" (oder?) Und b) Sie würden ein wachsendes Protokoll der Ausgabe von programB in der Bufferdatei erhalten Hagen von Eitzen vor 8 Jahren 0
@HagenvonEitzen a) Windows hat einen Schwanz b) Du kannst ihn durch einen gnu one gnuwin32 Download von Schwanz ersetzen. Windows hat also durchaus einen Endbefehl. barlop vor 8 Jahren 0
@barlop Ich bin mir ziemlich sicher, dass Windows standardmäßig kein Tail hat. (tippte "tail" in cmd ein und "got" wurde nicht als interner oder externer Befehl, als bedienbares Programm oder als Batch-Datei erkannt. ") DarthRubik vor 8 Jahren 0
@DarthRubik ah, du hast recht, es ist Windows Server 2003-Tools (und vielleicht auch später), obwohl es schon eine Weile her ist. Obwohl das iirc gnuwin32-Endstück besser ist, bietet es mehr Optionen. barlop vor 8 Jahren 0

3 Antworten auf die Frage

5
dbenham

Es kann nicht nur gemacht werden, es kann nur eine Batch-Datei verwendet werden! :-)

Das Problem kann gelöst werden, indem eine temporäre Datei als "Pipe" verwendet wird. Die bidirektionale Kommunikation erfordert zwei "Pipe" -Dateien.

Prozess A liest stdin aus "pipe1" und schreibt stdout in "pipe2".
Prozess B liest stdin aus "pipe2" und schreibt stdout in "pipe1".

Es ist wichtig, dass beide Dateien vorhanden sind, bevor einer der beiden Prozesse gestartet wird. Die Dateien sollten zu Beginn leer sein.

Wenn eine Stapeldatei versucht, aus einer Datei zu lesen, die sich gerade am aktuellen Ende befindet, gibt sie einfach nichts zurück und die Datei bleibt geöffnet. Meine readLine-Routine liest also kontinuierlich, bis sie einen nicht leeren Wert erhält.

Ich möchte in der Lage sein, eine leere Zeichenfolge zu lesen und zu schreiben, sodass meine writeLine-Routine ein zusätzliches Zeichen anfügt, das von der readLine entfernt wird.

Mein A-Prozess steuert den Fluss. Es initiiert Dinge durch Schreiben von 1 (Nachricht an B) und führt dann eine Schleife mit 10 Iterationen durch, in der ein Wert (Nachricht von B) gelesen wird, 1 hinzugefügt wird und das Ergebnis (Nachricht an B) geschrieben wird. Schließlich wartet er auf die letzte Nachricht von B, schreibt dann eine "Quit" -Nachricht an B und beendet den Vorgang.

Mein B-Prozess befindet sich in einer bedingt endlosen Schleife, die einen Wert (Nachricht von A) liest, 10 addiert und dann das Ergebnis (Nachricht an A) schreibt. Wenn B jemals eine "Quit" -Meldung liest, wird sie sofort abgebrochen.

Ich wollte zeigen, dass die Kommunikation vollständig synchron ist. Daher führe ich eine Verzögerung in den Prozessschleifen A und B ein.

Beachten Sie, dass sich die readLine-Prozedur in einer engen Schleife befindet, die sowohl die CPU als auch das Dateisystem ständig missbraucht, während sie auf Eingaben wartet. Eine PING-Verzögerung könnte der Schleife hinzugefügt werden, aber dann reagieren die Prozesse nicht so schnell.

Ich benutze eine echte Pipe, um sowohl den A- als auch den B-Prozess zu starten. Die Pipe ist jedoch nicht funktionsfähig, da keine Kommunikation durch sie hindurchgeht. Die gesamte Kommunikation erfolgt über meine temporären "Pipe" -Dateien.

Ich hätte START / B genauso gut zum Starten der Prozesse verwenden können, aber dann muss ich erkennen, wann beide beendet werden, damit ich weiß, wann die temporären "Pipe" -Dateien gelöscht werden. Es ist viel einfacher, das Rohr zu verwenden.

Ich habe mich entschieden, den gesamten Code in eine einzige Datei zu packen - das Master-Skript, das A und B startet, sowie den Code für A und B. Ich hätte für jeden Prozess eine eigene Skript-Datei verwenden können.

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 

--AUSGABE--

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 

Das Leben ist mit einer höheren Niveausprache etwas einfacher. Im Folgenden finden Sie ein Beispiel, in dem VBScript für die A- und B-Prozesse verwendet wird. Ich benutze immer noch Batch, um die Prozesse zu starten. Ich verwende eine sehr coole Methode. Kann VBScript in eine Batchdatei eingebettet und ausgeführt werden, ohne eine temporäre Datei zu verwenden? Einbetten mehrerer VBS-Skripts in ein einzelnes Batch-Skript.

Mit einer höheren Sprache wie VBS können wir eine normale Pipe verwenden, um Informationen von A an B zu übergeben. Wir benötigen nur eine einzige temporäre "Pipe" -Datei, um Informationen von B zurück an A zu übergeben. Weil wir jetzt eine funktionierende Pipe haben, das A Der Prozess muss keine "Quit" -Nachricht an B senden. Der B-Prozess durchläuft einfach eine Schleife, bis er das Dateiende erreicht.

Es ist sicher schön, Zugang zu einer richtigen Schlaffunktion in VBS zu haben. Dadurch kann ich leicht eine kurze Verzögerung in die readLine-Funktion einfügen, um die CPU zu unterbrechen.

Allerdings gibt es innerhalb von readLIne eine Falte. Zuerst bekam ich intermittierende Fehler, bis mir klar wurde, dass readLine manchmal Informationen über stdin zur Verfügung stellte und sofort versuchte, die Zeile zu lesen, bevor B die Möglichkeit hatte, die Zeile fertig zu schreiben. Ich löste das Problem, indem ich eine kurze Verzögerung zwischen dem Dateiende-Test und dem Lesen einführte. Eine Verzögerung von 5 ms schien den Trick für mich zu erfüllen, aber ich verdoppelte diese auf 10 ms, um auf der sicheren Seite zu sein. Es ist sehr interessant, dass Batch dieses Problem nicht erleidet. Wir haben dies kurz besprochen (5 kurze Beiträge) unter 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> 

Die Ausgabe ist dieselbe wie bei der reinen Batch-Lösung, außer dass die abschließenden "Quit" -Linien nicht vorhanden sind.

4
barlop

Hinweis: Im Nachhinein, wenn Sie die Frage noch einmal lesen, wird nicht das getan, was gefragt wird. Da es zwar zwei Prozesse miteinander verbindet (auf eine interessante Weise, die sogar über ein Netzwerk funktionieren würde!), Verbindet es jedoch nicht beide Richtungen.


Ich hoffe, du bekommst ein paar Antworten darauf.

Hier ist meine Antwort, aber akzeptiere sie nicht, warte auf andere Antworten, ich bin gespannt auf andere Antworten.

Dies wurde von cygwin gemacht. Und mit dem Befehl 'nc' (der clevere). Das 'wc -l' zählt nur Zeilen. Ich verlinke also alle zwei Befehle, in diesem Fall Echo und WC, mit NC.

Der Befehl auf der linken Seite wurde zuerst ausgeführt.

nc ist ein Befehl, der a) einen Server erstellen oder b) wie der Telnet-Befehl im Raw-Modus eine Verbindung zu einem Server herstellen kann. Ich verwende die Verwendung von 'a' im linken Befehl und die Verwendung von 'b' im rechten Befehl.

Also saß nc da und lauschte auf eine Eingabe, und dann leitete sie diese Eingabe zu, wc -lzählte die Zeilen und gab die Anzahl der eingegebenen Zeilen aus.

Ich ließ dann die Zeile laufen, um etwas Text zu wiederholen und das Rohdaten an 127.0.0.1:123 zu senden, bei dem es sich um den erwähnten Server handelt.

Sie können den Befehl nc.exe von cygwin kopieren und versuchen, die Datei und die Datei cygwin1.dll im selben Verzeichnis zu verwenden. Oder Sie könnten es von cygwin selbst tun, so wie ich. Ich sehe nc.exe nicht in gnuwin32. Sie haben eine Suche http://gnuwin32.sourceforge.net/ und nc oder netcat erscheint nicht. Sie können cygwin jedoch https://cygwin.com/install.html erhalten

Ich habe zehnmal gebraucht, um das alles durchzulesen, bevor ich verstanden habe, was los war ... das ist ziemlich schlau DarthRubik vor 8 Jahren 0
@DarthRubik Ja, und Sie können `netstat -aon | verwenden find ": 123" `, um zu sehen, dass der Befehl auf der linken Seite den Server erstellt hat barlop vor 8 Jahren 0
Aber wie kommunizieren Sie in die andere Richtung (dh vom `wc` zurück zum` echo`-Befehl)? DarthRubik vor 8 Jahren 0
Wenn ich mit nc versuche, es auf beide Arten zu tun, kann ich es nicht zum Laufen bringen, vielleicht geht nc in eine Schleife. barlop vor 8 Jahren 0
`nc -lp9999 | prog1 | prog2 | nc 127.0.0.1 9999` Es müssen möglicherweise Schritte unternommen werden, um sicherzustellen, dass die Puffer rechtzeitig gespült werden Jasen vor 8 Jahren 0
@Jasen danke, ich werde es versuchen. Übrigens, Cygwin verwendet das bsd nc, das 'nc -l' und nicht 'nc -lp' verwendet barlop vor 8 Jahren 0
Haben Sie @Jasen oder Darth ein Beispiel für prog1 und prog2, wo es produziert oder etwas produzieren würde, das zeigt, dass es wie beabsichtigt funktioniert? barlop vor 8 Jahren 0
das ist etwas, das stderr schreibt Ich kann mir im Moment kein gutes Beispiel vorstellen, Jasen vor 8 Jahren 0
@Jasen Ja, ich dachte darüber nach, an Stderr zu schreiben. Ich habe Folgendes versucht: http://pastebin.com/raw/QB1r1xNW Ist die NC-Zeile, die Sie erwähnt haben, rekursiv? Ich denke, das ist es. Aber das Programm, das ich gerade geschrieben habe, um ein Zeichen von einer Zeichenfolge zu entfernen, und mit der nc-Zeile, die Sie erwähnen, nimmt nicht mehr als zwei Zeichen von einer Zeichenfolge ab, als würde es nur zweimal aufgerufen. barlop vor 8 Jahren 0
@Jasen deshalb bin ich mir nicht sicher, ob deine 'nc -lp9999 | prog1 | prog2 | nc 127.0.0.1 9999` arbeitet wie vorgesehen barlop vor 8 Jahren 0
Man hat mir gesagt, es sollte "-l 9999" sein, und den Punkt zum Puffern beachten user313114 vor 8 Jahren 0
2
DarthRubik

Ein Hack (ich würde es vorziehen, dies nicht zu tun, aber das ist, was ich jetzt vorhabe) ist, eine C # -Anwendung zu schreiben, um dies für Sie zu tun. Ich habe einige wichtige Funktionen in diesem Programm nicht implementiert (z. B. indem ich die mir gegebenen Argumente verwende), aber hier sind es:

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

Wenn dieses Programm dann voll funktionsfähig ist und der Debug-Code entfernt wird, verwenden Sie es in etwa wie folgt:

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