Dois programas com StdIn e StdOut vinculados

12

Suponha que eu tenha dois programas chamados ProgramAe ProgramB. Quero executá-los simultaneamente no interpretador de cmd do Windows. Mas eu quero o StdOutde ProgramAgancho para o StdInde ProgramBe o StdOutde ProgramBgancho para o StdInde ProgramA.

Algo assim

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

Existe algum comando para fazer isso - alguma maneira de obter essa funcionalidade do cmd?

DarthRubik
fonte
4
No unix, eu usaria pipes nomeados. Para fazer isso, o Windows tem algo chamado pipes nomeados que é completamente diferente e possivelmente não aplicável.
Jasen
@ Jason, de acordo com um comentário aqui linuxjournal.com/article/2156 pipes nomeados podem funcionar no cygwin .. se sim, então talvez você possa elaborar e postar como resposta, embora valha a pena testar primeiro no cygwin
barlop
Suponho que os programas também precisariam ser escritos para não fazer um loop. Portanto, se o stdout for uma linha vazia, não alimente o stdin e, se nada for stdin, saia. Evitando assim um loop infinito? Mas por interesse, qual é a aplicação?
Barlop
2
diz que você quer ter duas instâncias de "Eliza" conversando?
Jasen
Se os programas não forem cuidadosamente projetados para lidar com isso, eles podem entrar em um impasse - ambos aguardam a entrada do outro e nada acontece (ou ambos estão tentando gravar em buffers completos e nunca leem nada para esvaziá-los) )
Random832

Respostas:

5

Não apenas isso pode ser feito, mas apenas com um arquivo em lotes! :-)

O problema pode ser resolvido usando um arquivo temporário como um "pipe". A comunicação bidirecional requer dois arquivos "pipe".

Processo A lê stdin de "pipe1" e grava stdout em "pipe2"
Processo B lê stdin de "pipe2" e grava stdout em "pipe1"

É importante que os dois arquivos existam antes do início de qualquer processo. Os arquivos devem estar vazios no início.

Se um arquivo em lotes tentar ler um arquivo que está no final atual, ele simplesmente não retornará nada e o arquivo permanecerá aberto. Portanto, minha rotina readLine lê continuamente até obter um valor não vazio.

Quero poder ler e escrever uma sequência vazia, portanto, minha rotina writeLine acrescenta um caractere extra que a readLine retira.

Meu processo A controla o fluxo. Ele inicia as coisas escrevendo 1 (mensagem para B) e entra em um loop com 10 iterações, onde lê um valor (mensagem de B), adiciona 1 e depois grava o resultado (mensagem para B). Por fim, aguarda a última mensagem de B e, em seguida, grava uma mensagem "encerrar" em B e sai.

Meu processo B está em um loop condicionalmente interminável que lê um valor (mensagem de A), adiciona 10 e depois grava o resultado (mensagem em A). Se B alguma vez ler uma mensagem "encerrar", ela será encerrada imediatamente.

Queria demonstrar que a comunicação é totalmente síncrona, por isso introduzo um atraso nos loops do processo A e B.

Observe que o procedimento readLine está em um loop restrito que abusa continuamente a CPU e o sistema de arquivos enquanto aguarda a entrada. Um atraso PING pode ser adicionado ao loop, mas os processos não serão tão responsivos.

Eu uso um verdadeiro pipe como uma conveniência para iniciar os processos A e B. Mas o tubo não é funcional, pois nenhuma comunicação passa por ele. Toda a comunicação é feita através dos meus arquivos "pipe" temporários.

Eu poderia ter usado o START / B para iniciar os processos, mas preciso detectar quando os dois terminam, para saber quando excluir os arquivos "pipe" temporários. É muito mais simples usar o tubo.

Eu escolhi colocar todo o código em um único arquivo - o script mestre que inicia A e B, bem como o código para A e B. Eu poderia ter usado um arquivo de script separado para cada 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

--RESULTADO--

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

A vida é um pouco mais fácil com uma linguagem de nível superior. Abaixo está um exemplo que usa VBScript para os processos A e B. Eu ainda uso o lote para iniciar os processos. Eu uso um método muito interessante descrito em É possível incorporar e executar o VBScript em um arquivo em lotes sem usar um arquivo temporário? para incorporar vários scripts VBS em um único script em lote.

Com um idioma mais alto como o VBS, podemos usar um canal normal para passar informações de A para B. Precisamos apenas de um único arquivo "canal" temporário para passar informações de B de volta para A. Como agora temos um canal em funcionamento, o A O processo não precisa enviar uma mensagem "encerrar" para B. O processo B simplesmente faz um loop até atingir o final do arquivo.

É bom ter acesso a uma função de suspensão adequada no VBS. Isso me permite introduzir facilmente um pequeno atraso na função readLine para dar uma pausa na CPU.

No entanto, há uma ruga no readLIne. No começo, eu estava recebendo falhas intermitentes até perceber que, às vezes, o readLine detectava informações disponíveis no stdin e tentava imediatamente ler a linha antes que B tivesse a chance de terminar de escrever a linha. Resolvi o problema introduzindo um pequeno atraso entre o teste de fim de arquivo e a leitura. Um atraso de 5 ms parecia fazer o truque para mim, mas eu dobrei para 10 ms apenas por segurança. É muito interessante que o lote não sofra esse problema. Discutimos isso brevemente (5 postagens curtas) em 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>

A saída é a mesma da solução em lote puro, exceto que as linhas finais "encerradas" não estão lá.

dbenham
fonte
4

Nota- Em retrospecto, lendo a pergunta novamente, isso não faz o que é solicitado. Como, apesar de vincular dois processos, (de uma maneira interessante que funcionaria em uma rede!), Ele não vincula os dois lados.


Espero que você obtenha algumas respostas para isso.

Aqui está a minha resposta, mas não a aceite, aguarde outras respostas, estou interessado em ver algumas outras respostas.

Isso foi feito a partir do cygwin. E usando o comando 'nc' (o mais inteligente). O 'wc -l' conta apenas as linhas. Então, eu estou ligando quaisquer dois comandos, neste caso, echo e wc, usando nc.

O comando à esquerda foi feito primeiro.

nc é um comando capaz de: a) criar um servidor ou b) conectar-se como o comando telnet no modo bruto, a um servidor. Estou usando o uso 'a' no comando esquerdo e o uso 'b' no comando direito.

Então nc ficou lá, ouvindo, esperando por uma entrada, e depois canalizou essa entrada wc -le contou as linhas e emitiu o número de linhas inseridas.

Em seguida, corri a linha para repetir algum texto e enviá-lo para 127.0.0.1:123, que é o servidor mencionado.

insira a descrição da imagem aqui

Você pode copiar o comando nc.exe do cygwin e tentar usar esse arquivo e o arquivo cygwin1.dll que ele precisa no mesmo diretório. Ou você pode fazê-lo do próprio cygwin como eu. Não vejo o nc.exe no gnuwin32. Eles têm uma pesquisa http://gnuwin32.sourceforge.net/ e nc ou netcat não aparece. Mas você pode obter o cygwin https://cygwin.com/install.html

barlop
fonte
Demorei dez vezes para ler isso antes de entender o que estava acontecendo ... isso é bastante inteligente #
DarthRubik
@DarthRubik sim, e você pode usar netstat -aon | find ":123"para ver que o comando à esquerda criou o servidor
barlop
Mas como você comunica a outra direção (ou seja, da parte wctraseira ao echocomando).
DarthRubik
quando tento com o nc fazer as duas coisas que não consigo, talvez o nc entre em um loop de algum tipo.
barlop
nc -lp9999 | prog1 | prog2 | nc 127.0.0.1 9999passos podem precisar de ser tomadas para garantir buffers são liberados em tempo hábil
Jasen
2

Um truque (eu preferiria não fazer isso, mas é com isso que eu vou por enquanto) é escrever um aplicativo C # para fazer isso por você. Eu não implementei alguns recursos importantes neste programa (como realmente usar os argumentos que me foram dados), mas aqui 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();

        }
    }
}

Eventualmente, quando este programa estiver totalmente funcional e o código de depuração for removido, você usaria algo como isto:

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