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

1452
DarthRubik

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

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

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

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

11
В Unix я бы использовал именованные каналы, для этого в Windows есть нечто, называемое именованные каналы, которое совершенно другое и, возможно, неприменимо. Jasen 7 лет назад 4
@Jasen в соответствии с комментарием здесь http://www.linuxjournal.com/article/2156 именованные каналы могут работать на cygwin .. если так, то, возможно, вы могли бы разработать и опубликовать в качестве ответа, хотя стоит сначала протестировать на cygwin barlop 7 лет назад 0
Я полагаю, что программы также должны были бы быть написаны так, чтобы они не зацикливались. Так что, если stdout - пустая строка, не передавайте в stdin, и, если ничто не является stdin, затем выходите. Таким образом, избегая бесконечного цикла? Но из интереса, что за приложение? barlop 7 лет назад 0
говорите, хотите, чтобы у двух экземпляров "Элизы" был разговор? Jasen 7 лет назад 2
Если программы не разработаны тщательно, чтобы справиться с этим, они могут зайти в тупик - оба ждут ввода от другого и ничего не происходит (или обе они пытаются записать в полные буферы и никогда не читают что-либо, чтобы очистить их) ) Random832 7 лет назад 0
Что-то вроде `tail -f bufferfile | Программа А | ProgramB >> bufferfile` может работать, но а) я думаю, что в Windows нет `tail` (или нет?) И б) вы получите растущий журнал вывода programB в bufferfile Hagen von Eitzen 7 лет назад 0
@HagenvonEitzen a) У windows есть хвост b) Вы можете заменить его на gnu one gnuwin32 download of tail. Таким образом, окна, безусловно, имеют и могут иметь команду хвоста. barlop 7 лет назад 0
@ barlop Я вполне уверен, что у окон нет хвоста по умолчанию. (напечатанный "tail" в cmd и получивший "tail" не распознается как внутренняя или внешняя команда, работающая программа или пакетный файл. ") DarthRubik 7 лет назад 0
@DarthRubik Ах, вы правы, это инструменты для Windows Server 2003 (и, возможно, позже), хотя это уже давно произошло. Хотя iirc gnuwin32 tail лучше, у него больше возможностей. barlop 7 лет назад 0

3 ответа на вопрос

5
dbenham

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

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

Процесс 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#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> 

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

4
barlop

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


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

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

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

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

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

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

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

enter image description here

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

Мне понадобилось десять раз, чтобы прочитать это, прежде чем я понял, что происходит ..... это довольно умно DarthRubik 7 лет назад 0
@DarthRubik да, и вы можете использовать `netstat -aon | find ": 123" `чтобы увидеть, что команда слева создала сервер barlop 7 лет назад 0
Но как вы сообщаете другое направление (т. Е. От `wc` до команды` echo`). DarthRubik 7 лет назад 0
Когда я пытаюсь с помощью nc сделать это обоими способами, я не могу заставить его работать, возможно, nc зацикливается. barlop 7 лет назад 0
`nc -lp9999 | prog1 | прог2 | nc 127.0.0.1 9999` шаги могут потребоваться для своевременного сброса буферов Jasen 7 лет назад 0
@Jasen спасибо, я попробую .. кстати, cygwin использует bsd nc, который использует `nc -l`, а не` nc -lp` barlop 7 лет назад 0
У вас, @Jasen или Darth, есть примеры prog1 и prog2, где он производит или выдаст что-то, что показывает, что он функционирует так, как задумано? barlop 7 лет назад 0
это будет то, что пишет в stderr, я не могу придумать хорошего примера в данный момент, Jasen 7 лет назад 0
@Jasen да, я тоже думал писать в stderr .. Я попробовал это http://pastebin.com/raw/QB1r1xNW Является ли упомянутая вами строка nc рекурсивной? Я предполагаю, что это так. Но программа, которую я только что написал, чтобы убрать один символ из строки, и с упомянутой вами строкой nc, не берет из строки более двух символов, как будто она вызывается только дважды. barlop 7 лет назад 0
@Jasen, поэтому я не уверен, что твой `nc -lp9999 | prog1 | прог2 | nc 127.0.0.1 9999` работает как надо barlop 7 лет назад 0
Мне сказали, что это должно быть `-l 9999`, и обратите внимание на смысл буферизации user313114 7 лет назад 0
2
DarthRubik

Один из способов взлома (я бы предпочел не делать этого, но сейчас это то, чем я занимаюсь) - это написать приложение на 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"