假设我有两个程序分别名为ProgramA
和ProgramB
。我想在 Windows cmd 解释器中同时运行它们。但我希望 的StdOut
连接ProgramA
到StdIn
的ProgramB
, 的StdOut
连接ProgramB
到StdIn
的ProgramA
。
像这样
________________ ________________ | | | | | 标准输入(== ← === ← ==(标准输出 | | 方案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"