Zwei Programme, bei denen StdIn und StdOut gebunden sind


12

Angenommen, ich habe zwei Programme namens ProgramAund ProgramB. Ich möchte beide gleichzeitig im Windows-Cmd-Interpreter ausführen. Aber ich möchte das StdOutvon ProgramAsüchtig nach dem StdInvon ProgramBund das StdOutvon ProgramBsüchtig nach dem StdInvon ProgramA.

Etwas wie das

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

Gibt es einen Befehl, um dies zu tun - eine Möglichkeit, diese Funktionalität von cmd aus zu erreichen?


4
Unter Unix würde ich Named Pipes verwenden. Zu diesem Zweck hat Windows so genannte Named Pipes, die völlig anders und möglicherweise nicht anwendbar sind.
Jasen

@Jasen laut einem Kommentar hier könnte linuxjournal.com/article/2156 namens pipes auf Cygwin funktionieren .. wenn ja, dann könnten Sie vielleicht als Antwort ausarbeiten und posten, obwohl es sich lohnt, zuerst auf Cygwin zu testen
barlop

Ich nehme an, die Programme müssten auch so geschrieben werden, dass sie keine Schleife bilden. Wenn also stdout eine leere Zeile ist, dann füttere nicht stdin, und wenn nichts stdin ist, dann beende. So vermeiden Sie eine Endlosschleife? Aber aus Interesse, was ist die Anwendung?
Barlop

2
Angenommen, Sie möchten, dass zwei Instanzen von "Eliza" ein Gespräch führen?
Jasen

Wenn die Programme nicht sorgfältig darauf ausgelegt sind, können sie in einen Deadlock geraten - beide warten auf Eingaben des anderen und es passiert nie etwas (oder beide versuchen, in volle Puffer zu schreiben und lesen nie etwas, um sie zu leeren )
Random832

Antworten:


5

Dies ist nicht nur möglich, sondern auch nur mit einer Batch-Datei! :-)

Das Problem kann gelöst werden, indem eine temporäre Datei als "Pipe" verwendet wird. Für die bidirektionale Kommunikation sind zwei "Pipe" -Dateien erforderlich.

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

Es ist wichtig, dass beide Dateien vorhanden sind, bevor Sie einen der beiden Prozesse starten. Die Dateien sollten zu Beginn leer sein.

Wenn eine Batchdatei versucht, aus einer Datei zu lesen, die sich gerade am aktuellen Ende befindet, wird einfach nichts zurückgegeben, 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, daher hängt meine writeLine-Routine ein zusätzliches Zeichen an, das die readLine entfernt.

Mein A-Prozess steuert den Fluss. Es initiiert Dinge durch Schreiben von 1 (Nachricht an B) und tritt dann in eine Schleife mit 10 Iterationen ein, in der es einen Wert liest (Nachricht von B), 1 addiert und dann das Ergebnis schreibt (Nachricht an B). Schließlich wartet es auf die letzte Nachricht von B und schreibt dann eine "Beenden" -Nachricht an B und wird beendet.

Mein B-Prozess befindet sich in einer bedingten Endlosschleife, die einen Wert liest (Nachricht von A), 10 addiert und dann das Ergebnis schreibt (Nachricht an A). Wenn B jemals eine "Beenden" -Nachricht liest, wird sie sofort beendet.

Ich wollte zeigen, dass die Kommunikation vollständig synchron ist, daher füge ich eine Verzögerung sowohl in der A- als auch in der B-Prozessschleife ein.

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

Ich verwende 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 geleitet wird. Die gesamte Kommunikation erfolgt über meine temporären "Pipe" -Dateien.

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

Ich habe mich dafür entschieden, den gesamten Code in eine einzige Datei zu packen - das Masterskript, mit dem A und B gestartet werden, sowie den Code für A und B. Ich hätte für jeden Prozess eine separate Skriptdatei 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

Mit einer höheren Sprache ist das Leben ein bisschen einfacher. Unten finden Sie ein Beispiel, das VBScript für die Prozesse A und B verwendet. Ich benutze immer noch Batch, um die Prozesse zu starten. Ich verwende eine sehr coole Methode, die unter Ist es möglich, VBScript in eine Batchdatei einzubetten und auszuführen, ohne eine temporäre Datei zu verwenden? um mehrere VBS-Skripte in ein einziges Batch-Skript einzubetten.

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

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ühren, um der CPU eine Pause zu geben.

Es gibt jedoch eine Falte in readLIne. Zuerst bekam ich zeitweise Fehler, bis mir klar wurde, dass readLine manchmal feststellte, dass Informationen auf stdin verfügbar sind, und sofort versuchte, die Zeile zu lesen, bevor B die Möglichkeit hatte, das Schreiben der Zeile abzuschließen. Ich habe das Problem gelöst, indem ich eine kurze Verzögerung zwischen dem Dateiende-Test und dem Lesen eingeführt habe. Eine Verzögerung von 5 ms schien den Trick für mich zu tun, aber ich habe das auf 10 ms verdoppelt, nur um auf der sicheren Seite zu sein. Es ist sehr interessant, dass bei Batch dieses Problem nicht auftritt. 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 die gleiche wie bei der reinen Batch-Lösung, außer dass die letzten "Quit" -Zeilen nicht vorhanden sind.


4

Hinweis: Wenn Sie die Frage im Nachhinein 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!), Verknüpft es jedoch nicht beide Wege.


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' (dem klugen). Das 'wc -l' zählt nur Zeilen. Also verknüpfe ich zwei beliebige Befehle, in diesem Fall echo und wc, mit nc.

Der Befehl links 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 'a' im linken Befehl und die Verwendung 'b' im rechten Befehl.

Also saß nc da und lauschte auf eine Eingabe und leitete diese Eingabe an wc -ldie Zeilen weiter, zählte sie und gab die Anzahl der eingegebenen Zeilen aus.

Ich habe dann die Zeile ausgeführt, um einen Text wiederzugeben und diesen Rohdaten an 127.0.0.1:123 zu senden, den genannten Server.

Geben Sie hier die Bildbeschreibung ein

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



@DarthRubik ja, und Sie können netstat -aon | find ":123"sehen, dass der Befehl auf der linken Seite den Server erstellt hat
Barlop

Aber wie kommuniziert man die andere Richtung (dh von wchinten zum echoBefehl).
DarthRubik

Wenn ich mit nc versuche, es in beide Richtungen zu tun, kann ich es nicht zum Laufen bringen, vielleicht gerät nc in eine Schleife.
Barlop

nc -lp9999 | prog1 | prog2 | nc 127.0.0.1 9999Möglicherweise müssen Schritte unternommen werden, um sicherzustellen, dass die Puffer rechtzeitig
geleert

2

Ein Hack (ich würde es vorziehen, dies nicht zu tun, aber das ist, was ich jetzt mache ) 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. die Verwendung der mir gegebenen Argumente), aber hier ist 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 wurde, würden Sie es ungefähr so ​​verwenden:

joiner "A A's args" "B B's Args"
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.