Две программы со связанными 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"