Дві програми, які мають пов'язані StdIn та StdOut


12

Припустимо, у мене є дві програми під назвою ProgramAта ProgramB. Я хочу запустити їх одночасно в інтерпретаторі Windows cmd. Але я хочу, щоб StdOutз ProgramAзачепили до StdInпро ProgramBі StdOutпро ProgramBзачепили до StdInпро ProgramA.

Щось на зразок цього

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

Чи є якась команда для цього - якимось чином досягти цієї функціональності від cmd?


4
У unix я б використовував названі труби, щоб зробити це, у Windows є щось, що називається, названі труби, що зовсім інше і, можливо, не застосовується.
Ясен

@Jasen відповідно до коментаря тут linuxjournal.com/article/2156 з назвою труби можуть працювати на cygwin .. якщо так, то, можливо, ви можете розробити та опублікувати як відповідь, хоча варто перевірити спочатку cygwin
барлоп

Я припускаю, що програми також повинні бути написані так, щоб не циклічно. Отже, якщо stdout - це порожній рядок, тоді не слід подавати на stdin, і якщо нічого не є stdin, тоді вийдіть. Таким чином уникнути нескінченного циклу? Але з інтересу, що це за додаток?
барлоп

2
скажіть, ви хочете, щоб два екземпляри "Елізи" мали розмову?
Ясен

Якщо програми не ретельно розроблені для вирішення цього питання, вони можуть потрапити в глухий кут - обидва чекають на введення від іншого і ніколи нічого не відбувається (або обидві намагаються записати в повні буфери і ніколи нічого не читають, щоб їх виповнити )
Випадково832

Відповіді:


5

Це не тільки можна зробити, це можна зробити лише за допомогою пакетного файлу! :-)

Проблему можна вирішити, використовуючи тимчасовий файл як "трубу". Двонаправлена ​​комунікація вимагає двох файлів "труби".

Процес A читає stdin з "pipe1" і записує stdout в "pipe2"
Процес B зчитує stdin з "pipe2" і записує stdout в "pipe1"

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

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

Я хочу вміти читати та писати порожній рядок, тому моя програма writeLine додає додатковий символ, який readLine знімає.

Мій процес контролює потік. Він ініціює речі, записуючи 1 (повідомлення B), а потім вводить цикл з 10 ітераціями, де він читає значення (повідомлення від B), додає 1, а потім записує результат (повідомлення B). Нарешті він чекає останнього повідомлення від B, а потім пише "вийти" з повідомлення в B і виходить.

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

Я хотів продемонструвати, що зв’язок повністю синхронний, тому я ввожу затримку як в циклі 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

--OUTPUT--

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. Нам потрібен лише один тимчасовий "трубовий" файл, щоб передати інформацію від B назад до A. Оскільки у нас зараз функціонує труба, A процес не потребує надсилання повідомлення "кинути" до B. Процес B просто циклізується, поки не досягне кінця файлу.

Звичайно, приємно мати доступ до належної функції сну в VBS. Це дозволяє мені легко запровадити коротку затримку функції readLine, щоб перервати процесор.

Однак у 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

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


Я сподіваюся, що ви отримаєте кілька відповідей на це.

Ось моя відповідь, але не приймайте її, чекайте інших відповідей, я хочу бачити деякі інші відповіді.

Це було зроблено від cygwin. І за допомогою команди 'nc' (розумний). 'Wc -l' просто рахує рядки. Тож я пов'язую будь-які дві команди, в даному випадку ехо та wc, використовуючи nc.

Команда зліва була виконана спочатку.

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

Тож nc сидів там і слухав, чекаючи введення, і тоді він передав би цей вхід wc -lі підрахував лінії та вивів кількість введених рядків.

Потім я провів рядок, щоб повторити текст і надіслати цей сирий на 127.0.0.1.123, який є згаданим сервером.

введіть тут опис зображення

Ви можете скопіювати команду nc.exe з cygwin і спробувати скористатися цим файлом і файлом cygwin1.dll, який їй потрібен в одному каталозі. Або ти міг це зробити із самого цигуна, як у мене. Я не бачу nc.exe в gnuwin32. Вони мають пошук http://gnuwin32.sourceforge.net/ і nc або netcat не з'являються. Але ви можете отримати cygwin https://cygwin.com/install.html


Мені потрібно було десять разів прочитати це, перш ніж я зрозумів, що відбувається ..... що досить розумно
DarthRubik

@DarthRubik так, і ви можете netstat -aon | find ":123"побачити, що команда зліва створила сервер
barlop

Але як ви повідомляєте інший напрямок (тобто зі wcспини до echoкоманди).
DarthRubik

коли я намагаюся з nc зробити це обома способами, я не можу змусити його працювати, можливо, nc переходить у певний цикл.
барлоп

nc -lp9999 | prog1 | prog2 | nc 127.0.0.1 9999може знадобитися вжити заходів, щоб забезпечити своєчасне промивання буферів
Ясен

2

Одним злом (я вважаю за краще не робити цього, але це те, з чим я зараз берусь) - це написати додаток 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"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.