绑定了StdIn和StdOut的两个程序


12

假设我有两个名为ProgramA和的程序ProgramB。我想在Windows cmd解释器中同时运行它们。但我想StdOutProgramA钩到StdInProgramBStdOutProgramB钩到StdInProgramA

像这样

 ________________ ________________
| | | |
| StdIn(==←===←==(StdOut |
| 程序A | | 程式B |
| | | |
| StdOut)==→===→==)StdIn |
| ________________ | | ________________ |

有什么命令可以执行此操作-通过cmd实现此功能的某种方法吗?


4
在Unix上,我将使用命名管道来执行此操作,而Windows具有称为“命名管道”的东西,这完全不同并且可能不适用。
杰森

@Jasen根据评论这里linuxjournal.com/article/2156 命名管道在Cygwin上可能工作..如果是的话,那么也许你可以详细说明和后一个答案,但值得测试在第一次的cygwin
barlop

我想程序也必须编写为不循环。.因此,如果stdout为空行,则不要输入stdin,如果没有什么输入stdin,则退出。从而避免无限循环?但是出于兴趣,应用程序是什么?
barlop

2
说您想让两个“伊丽莎”实例进行对话吗?
杰森

如果程序设计不当,可能会陷入死锁-两者都在等待对方的输入,并且什么也没有发生(或者它们都试图写入完整的缓冲区,并且从不读取任何内容以清空它们) )
Random832 '16

Answers:


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的代码。我可以为每个进程使用单独的脚本文件。

测试棒

@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会检测到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>

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


4

注意-回顾一下,再次阅读问题,这并没有问到什么。因为尽管它确实将两个进程链接在一起(以一种甚至可以通过网络运行的有趣方式!),但它并没有同时链接这两种方法。


希望您能对此得到一些答案。

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

这是从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


我花了十倍的时间阅读完这篇文章,才明白发生了什么……..那是相当聪明
DarthRubik

@DarthRubik是的,您可以netstat -aon | find ":123"用来查看左侧的命令已创建服务器
barlop

但是,您如何交流另一个方向(即,从wc背面到echo命令)。
DarthRubik '16

当我尝试使用nc来完成这两种方法而无法正常工作时,也许nc陷入了某种循环。
barlop

nc -lp9999 | prog1 | prog2 | nc 127.0.0.1 9999可能需要采取步骤以确保及时刷新缓冲区
Jasen

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"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.