11

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

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


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

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

3 ответа3

5

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

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

Процесс 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. Но канал не работает, так как через него не проходит никакой связи. Все общение происходит через мои временные файлы "pipe".

С тем же успехом я мог бы использовать 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. Поскольку у нас теперь есть работающий канал, процессу не нужно отправлять сообщение "выход" в 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

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


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

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

Это было сделано из 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

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"

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .