两个程序的 StdIn 和 StdOut 绑定

Dar*_*bik 12 windows redirection stdout stdin batch-file

假设我有两个名为ProgramA和 的程序ProgramB。我想在 Windows cmd 解释器中同时运行它们。但我希望StdOutofProgramA挂钩到StdInof ProgramB,而StdOutofProgramB挂钩到StdInof ProgramA

像这样的东西

 ________________ ________________
| | | |
| StdIn(== ? === ? ==(StdOut |
| 程序A | | 程序B |
| | | |
| 标准输出)== ? === ? ==)标准输入 |
|________________| |________________|

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

dbe*_*ham 5

不仅可以做到,只需要一个批处理文件就可以做到!:-)

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

进程 A 从“pipe1”读取 stdin 并将 stdout 写入“pipe2”
进程 B 从“pipe2”读取 stdin 并将 stdout 写入“pipe1”

在启动任一进程之前,这两个文件都存在是很重要的。这些文件在开始时应该是空的。

如果批处理文件试图从恰好位于当前末尾的文件中读取,它只会不返回任何内容,并且该文件保持打开状态。所以我的 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 的代码。我可以为每个进程使用一个单独的脚本文件。

测试.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
Run Code Online (Sandbox Code Playgroud)

- 输出 -

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
Run Code Online (Sandbox Code Playgroud)

使用高级语言,生活会轻松一些。下面是将 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 上对此进行了简要讨论(5 篇短文)。

<!-- : 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>
Run Code Online (Sandbox Code Playgroud)

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