Deux programmes ayant leur StdIn et StdOut liés


12

Supposons que j'ai deux programmes appelés ProgramAet ProgramB. Je veux les exécuter simultanément dans l'interpréteur Windows cmd. Mais je veux que le StdOutd' ProgramAaccroché à StdIndes ProgramBet StdOutdes ProgramBaccroché à StdIndes ProgramA.

Quelque chose comme ça

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

Existe-t-il une commande pour ce faire - un moyen d'obtenir cette fonctionnalité à partir de cmd?


4
Sous Unix, j'utiliserais des canaux nommés, pour ce faire, Windows a quelque chose appelé canaux nommés qui est complètement différent et peut-être pas applicable.
Jasen

@Jasen selon un commentaire ici linuxjournal.com/article/2156 pipes nommés pourraient travailler sur Cygwin .. si oui , alors vous pourriez peut - être élaborer et après comme une réponse, bien que les tests de valeur Cygwin premier
barlop

Je suppose que les programmes devraient également être écrits de manière à ne pas boucler. Donc, si la sortie standard est une ligne vide, ne pas alimenter stdin, et si rien ne doit être stdin, quittez. Évitant ainsi une boucle infinie? Mais par intérêt, quelle est l'application?
barlop

2
dites que vous voulez avoir deux instances de "Eliza" avoir une conversation?
Jasen

Si les programmes ne sont pas soigneusement conçus pour gérer cela, ils pourraient se retrouver dans une impasse - les deux attendent l'entrée de l'autre et rien ne se passe jamais (ou les deux essaient d'écrire dans des tampons complets et ne lisent jamais rien pour les vider) )
Random832

Réponses:


5

Non seulement cela peut être fait, mais cela ne peut être fait qu'avec un fichier batch! :-)

Le problème peut être résolu en utilisant un fichier temporaire comme "pipe". La communication bidirectionnelle nécessite deux fichiers "pipe".

Le processus A lit stdin de "pipe1" et écrit stdout dans "pipe2" Le
processus B lit stdin de "pipe2" et écrit stdout dans "pipe1"

Il est important que les deux fichiers existent avant de lancer l'un ou l'autre processus. Les fichiers doivent être vides au début.

Si un fichier de commandes tente de lire à partir d'un fichier qui se trouve être à la fin actuelle, il ne renvoie simplement rien et le fichier reste ouvert. Ainsi, ma routine readLine lit en continu jusqu'à ce qu'elle obtienne une valeur non vide.

Je veux pouvoir lire et écrire une chaîne vide, donc ma routine writeLine ajoute un caractère supplémentaire que readLine supprime.

Mon processus A contrôle le flux. Il initie les choses en écrivant 1 (message à B), puis entre dans une boucle avec 10 itérations où il lit une valeur (message de B), ajoute 1, puis écrit le résultat (message à B). Enfin, il attend le dernier message de B, puis écrit un message "quitter" dans B et quitte.

Mon processus B se trouve dans une boucle conditionnellement sans fin qui lit une valeur (message de A), ajoute 10, puis écrit le résultat (message dans A). Si B lit jamais un message "quitter", il se termine immédiatement.

Je voulais démontrer que la communication est entièrement synchrone, donc j'introduis un retard dans les boucles de processus A et B.

Notez que la procédure readLine est dans une boucle étroite qui abuse continuellement à la fois du CPU et du système de fichiers pendant qu'elle attend l'entrée. Un délai PING pourrait être ajouté à la boucle, mais les processus ne seront pas aussi réactifs.

J'utilise un vrai tuyau pour faciliter le lancement des processus A et B. Mais le tuyau n'est pas fonctionnel en ce sens qu'aucune communication ne le traverse. Toutes les communications se font via mes fichiers "pipe" temporaires.

J'aurais tout aussi bien pu utiliser START / B pour lancer les processus, mais je dois ensuite détecter quand ils se terminent tous les deux afin de savoir quand supprimer les fichiers temporaires "pipe". Il est beaucoup plus simple d'utiliser le tuyau.

J'ai choisi de mettre tout le code dans un seul fichier - le script principal qui lance A et B, ainsi que le code pour A et B. J'aurais pu utiliser un fichier de script distinct pour chaque processus.

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

--PRODUCTION--

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 vie est un peu plus facile avec une langue de niveau supérieur. Voici un exemple qui utilise VBScript pour les processus A et B. J'utilise toujours le batch pour lancer les processus. J'utilise une méthode très cool décrite dans Est-il possible d'incorporer et d'exécuter VBScript dans un fichier batch sans utiliser de fichier temporaire? pour incorporer plusieurs scripts VBS dans un même script batch.

Avec un langage plus élevé comme VBS, nous pouvons utiliser un canal normal pour transmettre des informations de A à B. Nous n'avons besoin que d'un seul fichier "canal" temporaire pour transmettre des informations de B à A. Parce que nous avons maintenant un canal fonctionnel, le A le processus n'a pas besoin d'envoyer un message "quitter" à B. Le processus B boucle simplement jusqu'à ce qu'il atteigne la fin du fichier.

C'est bien d'avoir accès à une fonction de veille appropriée dans VBS. Cela me permet d'introduire facilement un court délai dans la fonction readLine pour donner une pause au CPU.

Cependant, il y a une ride dans readLIne. Au début, j'obtenais des échecs intermittents jusqu'à ce que je réalise que parfois readLine détectait que des informations étaient disponibles sur stdin et essayait immédiatement de lire la ligne avant que B n'ait la chance de terminer l'écriture de la ligne. J'ai résolu le problème en introduisant un court délai entre le test de fin de fichier et la lecture. Un retard de 5 ms semblait faire l'affaire pour moi, mais je l'ai doublé à 10 ms juste pour être sûr. Il est très intéressant que le lot ne souffre pas de ce problème. Nous en avons discuté brièvement (5 courts messages) sur 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 sortie est la même que pour la solution par lots pure, sauf que les lignes finales de «sortie» ne sont pas là.


4

Remarque - Rétrospectivement, en relisant la question, cela ne fait pas ce qui est demandé. Depuis, alors qu'il relie deux processus ensemble (d'une manière intéressante qui fonctionnerait même sur un réseau!), Il ne relie pas les deux.


J'espère que vous obtiendrez quelques réponses à cela.

Voici ma réponse mais ne l'acceptez pas, attendez d'autres réponses, je suis impatient de voir d'autres réponses.

Cela a été fait à partir de cygwin. Et en utilisant la commande 'nc' (la plus intelligente). Le «wc -l» ne compte que les lignes. Je relie donc les deux commandes, dans ce cas, echo et wc, en utilisant nc.

La commande de gauche a été exécutée en premier.

nc est une commande qui peut a) créer un serveur, ou b) se connecter comme la commande telnet en mode brut, à un serveur. J'utilise l'utilisation «a» dans la commande de gauche et l'utilisation «b» dans la commande de droite.

Donc, nc était assis à écouter en attendant une entrée, puis il dirigeait cette entrée vers wc -let comptait les lignes et produisait le nombre de lignes entrées.

J'ai ensuite couru la ligne pour faire écho du texte et envoyer ce brut à 127.0.0.1:123 qui est le serveur mentionné.

entrez la description de l'image ici

Vous pouvez copier la commande nc.exe à partir de cygwin et essayer d'utiliser cela et le fichier cygwin1.dll dont il a besoin dans le même répertoire. Ou vous pouvez le faire à partir de cygwin lui-même comme je l'ai fait. Je ne vois pas nc.exe dans gnuwin32. Ils ont une recherche http://gnuwin32.sourceforge.net/ et nc ou netcat ne s'affiche pas. Mais vous pouvez obtenir cygwin https://cygwin.com/install.html


Il m'a fallu une dizaine de fois pour lire ceci avant de comprendre ce qui se passait ... c'est plutôt intelligent
DarthRubik

@DarthRubik ouais, et vous pouvez utiliser netstat -aon | find ":123"pour voir que la commande de gauche a créé le serveur
barlop

Mais comment communiquez-vous dans l'autre sens (c'est-à-dire de l' wcarrière vers la echocommande).
DarthRubik

quand j'essaie avec nc de le faire dans les deux sens, je ne peux pas le faire fonctionner, peut-être que nc entre dans une boucle quelconque.
barlop

nc -lp9999 | prog1 | prog2 | nc 127.0.0.1 9999des mesures devront peut-être être prises pour s'assurer que les tampons sont vidés en temps opportun
Jasen

2

Un hack (je préférerais ne pas le faire, mais c'est ce que je vais faire pour l'instant) consiste à écrire une application C # pour le faire pour vous. Je n'ai pas implémenté certaines fonctionnalités clés de ce programme (comme utiliser les arguments qui m'ont été fournis), mais voici:

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

        }
    }
}

Puis, finalement, lorsque ce programme sera pleinement fonctionnel et que le code de débogage sera supprimé, vous utiliserez quelque chose comme ceci:

joiner "A A's args" "B B's Args"
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.