Две программы со связанными StdIn и StdOut

Предположим, у меня есть две программы под названием ProgramA а также ProgramB, Я хочу запустить их обоих одновременно в интерпретаторе Windows cmd. Но я хочу StdOut из ProgramA подключен к StdIn из ProgramB и StdOut из ProgramB подключен к StdIn из ProgramA,

Что-то вроде этого

 ________________ ________________
| | | |
| StdIn (== ← === ← == (StdOut |
| Программа А | | Программа Б |
| | | |
| StdOut) == → === → ==) StdIn |
| ________________ | | ________________ |

Есть ли команда, чтобы сделать это - каким-то образом добиться этой функциональности из cmd?

3 ответа

Решение

Это может быть сделано не только с помощью командного файла!:-)

Проблема может быть решена путем использования временного файла в качестве "канала". Для двунаправленной связи требуются два "конвейерных" файла.

Процесс A читает стандартный ввод из "pipe1" и записывает стандартный вывод в "pipe2"
Процесс B читает стандартный вывод из "pipe2" и записывает стандартный вывод в "pipe1"

Важно, чтобы оба файла существовали до запуска любого процесса. Файлы должны быть пустыми в начале.

Если пакетный файл пытается прочитать из файла, который находится на текущем конце, он просто ничего не возвращает, и файл остается открытым. Так что моя подпрограмма readLine постоянно читает, пока не получит непустое значение.

Я хочу иметь возможность читать и писать пустую строку, поэтому моя подпрограмма writeLine добавляет дополнительный символ, который readLine удаляет.

Мой процесс A контролирует поток. Он инициирует вещи, записывая 1 (сообщение в B), а затем входит в цикл с 10 итерациями, где читает значение (сообщение из B), добавляет 1 и затем записывает результат (сообщение в B). Наконец, он ожидает последнего сообщения от B, а затем записывает сообщение "выход" в B и завершает работу.

Мой процесс B находится в условно бесконечном цикле, который читает значение (сообщение от A), добавляет 10, а затем записывает результат (сообщение в A). Если B когда-либо прочитает сообщение "выйти", оно немедленно прекратится.

Я хотел продемонстрировать, что связь полностью синхронна, поэтому я ввел задержку в обоих циклах процесса A и B.

Обратите внимание, что процедура readLine находится в узком цикле, который постоянно использует как процессор, так и файловую систему, ожидая ввода. Задержка PING может быть добавлена ​​к циклу, но тогда процессы не будут такими отзывчивыми.

Я использую настоящий канал для удобства запуска процессов A и B. Но канал не работает, так как через него не проходит никакой связи. Все общение происходит через мои временные "трубочные" файлы.

С таким же успехом я мог бы использовать START /B для запуска процессов, но затем я должен определить, когда они оба завершаются, чтобы я знал, когда удалять временные файлы "канала". Гораздо проще использовать трубу.

Я решил поместить весь код в один файл - главный скрипт, который запускает A и B, а также код для A и B. Я мог бы использовать отдельный файл скрипта для каждого процесса.

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

--ВЫХОД--

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

Жизнь немного легче с языком более высокого уровня. Ниже приведен пример, который использует VBScript для процессов A и B. Я все еще использую пакет для запуска процессов. Я использую очень крутой метод, описанный в разделе Возможно ли встраивать и выполнять VBScript в пакетном файле без использования временного файла? встроить несколько VBS-скриптов в один пакетный скрипт.

С более высоким языком, таким как VBS, мы можем использовать обычный канал для передачи информации от A к B. Нам нужен только один временный файл "pipe" для передачи информации от B обратно к A. Поскольку у нас теперь есть работающий канал, A процессу не нужно отправлять сообщение "выход" в B. Процесс B просто зацикливается, пока не достигнет конца файла.

Конечно, приятно иметь доступ к правильной функции сна в VBS. Это позволяет мне легко вводить небольшую задержку в функцию readLine, чтобы дать CPU перерыв.

Однако в readLIne есть одна морщина. Сначала я получал периодические сбои, пока не понял, что иногда readLine обнаруживает информацию, доступную на stdin, и сразу же пытается прочитать строку, прежде чем B сможет завершить написание строки. Я решил проблему, введя небольшую задержку между проверкой конца файла и чтением. Задержка в 5 мсек, казалось, сделала мой трюк, но я удвоил это до 10 мсек, чтобы быть в безопасности. Очень интересно, что партия не страдает этой проблемой. Мы кратко обсудили это (5 коротких постов) на http://www.dostips.com/forum/viewtopic.php?f=3&t=7078.

<!-- : 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>

Вывод такой же, как и для чистого пакетного решения, за исключением того, что конечные строки "выхода" отсутствуют.

Примечание. В ретроспективе, читая вопрос снова, это не делает то, что спрашивают. Поскольку, хотя он связывает два процесса вместе (интересным образом, который бы работал даже по сети!), Он не связывает оба пути.


Я надеюсь, что вы получите несколько ответов на это.

Вот мой ответ, но не принимайте его, ждите других ответов, мне очень хотелось бы увидеть другие ответы.

Это было сделано из Cygwin. И с помощью команды 'nc' (умная). 'Wc -l' просто считает строки. Так что я связываю любые две команды, в данном случае, echo и wc, используя nc.

Команда слева была сделана первой.

nc - это команда, которая может a) создать сервер или b) подключиться, как команда telnet в необработанном режиме, к серверу. Я использую использование 'a' в левой команде и использование 'b' в правой команде.

Так что nc сидел там, слушая, ожидая ввода, и затем передавал этот вход wc -l и подсчитать количество строк и вывести количество введенных строк.

Затем я запустил строку, чтобы отразить некоторый текст и отправить этот raw на 127.0.0.1:123, который является упомянутым сервером.

Вы можете скопировать команду nc.exe из cygwin и попробовать использовать ее и нужный файл cygwin1.dll в той же директории. Или вы можете сделать это из самого Cygwin, как я. Я не вижу nc.exe в gnuwin32. У них есть поиск http://gnuwin32.sourceforge.net/ и nc или netcat не подходят. Но вы можете получить Cygwin https://cygwin.com/install.html

Один из способов взлома (я бы предпочел не делать этого, но сейчас это то, чем я занимаюсь) - это написать приложение на C#, которое сделает это за вас. Я не реализовал некоторые ключевые функции в этой программе (например, фактически использовал предоставленные мне аргументы), но вот он:

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

        }
    }
}

Затем, в конце концов, когда эта программа станет полностью функциональной и код отладки будет удален, вы будете использовать его примерно так:

joiner "A A's args" "B B's Args"
Другие вопросы по тегам