两个程序的 StdIn 和 StdOut 绑定在一起

两个程序的 StdIn 和 StdOut 绑定在一起

假设我有两个程序分别名为ProgramAProgramB。我想在 Windows cmd 解释器中同时运行它们。但我希望 的StdOut连接ProgramAStdInProgramB, 的StdOut连接ProgramBStdInProgramA

像这样

________________ ________________
| | | |
| 标准输入(== ← === ← ==(标准输出 |
| 方案A | | 方案B |
| | | |
| StdOut)== → === → ==)StdIn |
|________________| |________________|

是否有任何命令可以执行此操作 - 可以通过某种方式从 cmd 实现此功能?

答案1

这不仅可以完成,而且只用批处理文件就可以完成!:-)

可以使用临时文件作为“管道”来解决该问题。双向通信需要两个“管道”文件。

进程 A 从“管道 1”读取标准输入,并将标准输出写入“管道 2”
进程 B 从“管道 2”读取标准输入,并将标准输出写入“管道 1”

在启动任一进程之前,这两个文件必须存在,这一点很重要。启动时这两个文件应该是空的。

如果批处理文件尝试读取恰好位于当前末尾的文件,它只会返回任何内容,并且文件保持打开状态。因此,我的 readLine 例程会持续读取,直到获得非空值。

我希望能够读取和写入一个空字符串,因此我的 writeLine 例程会附加一个额外的字符,而 readLine 会将其删除。

我的 A 进程控制流程。它通过写入 1(向 B 发送消息)来启动程序,然后进入一个包含 10 次迭代的循环,在其中读取一个值(来自 B 的消息),加 1,然后写入结果(向 B 发送消息)。最后,它等待来自 B 的最后一条消息,然后向 B 发送“退出”消息并退出。

我的 B 进程处于一个有条件的无限循环中,它读取一个值(来自 A 的消息),加 10,然后写入结果(发送给 A 的消息)。如果 B 读取了“退出”消息,则它会立即终止。

我想证明通信是完全同步的,所以我在 A 和 B 过程循环中都引入了延迟。

请注意,readLine 过程处于一个紧密循环中,在等待输入时,它会不断滥用 CPU 和文件系统。可以在循环中添加 PING 延迟,但这样进程的响应速度就会降低。

我使用真正的管道来方便地启动 A 和 B 进程。但是管道不起作用,因为没有通信通过它。所有通信都通过我的临时“管道”文件进行。

我也可以使用 START /B 来启动进程,但之后我必须检测它们何时终止,以便知道何时删除临时“管道”文件。使用管道要简单得多。

我选择将所有代码放在一个文件中 - 启动 A 和 B 的主脚本,以及 A 和 B 的代码。我可以为每个过程使用单独的脚本文件。

测试脚本

@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。我们只需要一个临时的“管道”文件将信息从 B 传回 A。因为我们现在有一个可以运行的管道,所以 A 进程不需要向 B 发送“退出”消息。B 进程只需循环,直到到达文件末尾。

在 VBS 中能够使用适当的睡眠函数确实很棒。这使我能够轻松地在 readLine 函数中引入短暂的延迟,让 CPU 休息一下。

但是 readLIne 有一个问题。起初我遇到了间歇性故障,直到我意识到有时 readLine 会检测到 stdin 上的信息,并会在 B 有机会完成写入之前立即尝试读取该行。我通过在文件结束测试和读取之间引入短暂延迟解决了这个问题。5 毫秒的延迟似乎对我有用,但为了安全起见,我将延迟时间加倍到 10 毫秒。非常有趣的是,批处理不会遇到这个问题。我们在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>

输出与纯批处理解决方案相同,只是没有最后的“退出”行。

答案2

注意:回想起来,再次阅读问题,这并没有达到所要求的效果。因为,虽然它确实将两个过程链接在一起(以一种有趣的方式,甚至可以通过网络工作!),但它并没有双向链接。


我希望你能得到一些答案。

这是我的答案,但不要接受它,等待其他答案,我很想看到其他答案。

这是通过 cygwin 完成的。并使用“nc”命令(聪明的那个)。“wc -l”只计算行数。所以我使用 nc 链接两个命令,在本例中为 echo 和 wc 。

左边的命令先完成。

nc 是一个能够 a) 创建服务器或 b) 像原始模式下的 telnet 命令一样连接到服务器的命令。我在左侧命令中使用“a”用法,在右侧命令中使用“b”用法。

因此 nc 坐在那里等待输入,然后它将该输入通过管道传输wc -l并计算行数并输出输入的行数。

然后我运行该行来回显一些文本并将原始文本发送到提到的服务器 127.0.0.1:123。

在此处输入图片描述

您可以从 cygwin 复制 nc.exe 命令,然后尝试在同一目录中使用该命令和它所需的 cygwin1.dll 文件。或者您可以像我一样从 cygwin 本身执行此操作。我在 gnuwin32 中没有看到 nc.exe。他们有一个搜索http://gnuwin32.sourceforge.net/ nc 或 netcat 没有出现。但你可以得到 cygwin https://cygwin.com/install.html

答案3

一个 hack(我不想这样做,但这是我现在要做的事情)是写一个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"

相关内容