web-dev-qa-db-ja.com

StdInとStdOutが関連付けられている2つのプログラム

ProgramAProgramBという2つのプログラムがあるとします。 Windowsのcmdインタープリターで両方を同時に実行したいと思います。しかし、私はStdOutProgramAStdInProgramBにフックし、StdOutProgramBをフックしたいStdInProgramA

このようなもの

 
 ________________ ________________ 
 | | | | 
 | StdIn(==←===←==(StdOut | 
 |プログラムA ||プログラムB | 
 | | | | 
 | StdOut)==→=== →==)StdIn | 
 | ________________ | | ________________ | 
 

これを行うコマンドはありますか?cmdからこの機能を実現する方法はありますか?

12
DarthRubik

実行できるだけでなく、バ​​ッチファイルだけで実行できます。 :-)

この問題は、一時ファイルを「パイプ」として使用することで解決できます。双方向通信には、2つの「パイプ」ファイルが必要です。

プロセスAは「pipe1」からstdinを読み取り、「pipe2」にstdoutを書き込みます。
プロセスBは「pipe2」からstdinを読み取り、「pipe1」にstdoutを書き込みます

どちらかのプロセスを起動する前に、両方のファイルが存在することが重要です。ファイルは最初は空である必要があります。

バッチファイルがたまたま現在の終わりにあるファイルから読み込もうとすると、何も返さず、ファイルは開いたままになります。したがって、私のreadLineルーチンは、空でない値を取得するまで継続的に読み取ります。

空の文字列を読み書きできるようにしたいので、writeLineルーチンは、readLineが削除する余分な文字を追加します。

MyAプロセスがフローを制御します。 1(Bへのメッセージ)を書き込むことで処理を開始し、10回の反復でループに入り、値(Bからのメッセージ)を読み取り、1を加算して、結果(Bへのメッセージ)を書き込みます。最後に、Bからの最後のメッセージを待ってから、「終了」メッセージをBに書き込んで終了します。

私のBプロセスは、値(Aからのメッセージ)を読み取り、10を加算して、結果(Aへのメッセージ)を書き込む条件付き無限ループにあります。 Bが「終了」メッセージを読み取ると、すぐに終了します。

通信が完全に同期していることを示したかったので、AプロセスループとBプロセスループの両方に遅延を導入します。

ReadLineプロシージャは、入力を待機している間、CPUとファイルシステムの両方を継続的に悪用するタイトループにあることに注意してください。 PING遅延がループに追加される可能性がありますが、その場合、プロセスはそれほど応答しなくなります。

AプロセスとBプロセスの両方を起動するための便宜のために、真のパイプを使用します。ただし、パイプは通信が通過しないという点で機能しません。すべての通信は、私の一時的な「パイプ」ファイルを介して行われます。

START/Bを使用してプロセスを起動することもできますが、一時的な「パイプ」ファイルをいつ削除するかを知るために、両方がいつ終了するかを検出する必要があります。パイプを使用する方がはるかに簡単です。

すべてのコードを1つのファイル(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

高水準言語を使用すると、生活が少し楽になります。以下は、AプロセスとBプロセスにVBScriptを使用する例です。私はまだバッチを使用してプロセスを起動します。 一時ファイルを使用せずにバッチファイル内にVBScriptを埋め込んで実行することは可能ですか? で説明されている非常に優れた方法を使用して、単一のバッチスクリプト内に複数のVBSスクリプトを埋め込みます。

VBSのようなより高い言語では、通常のパイプを使用してAからBに情報を渡すことができます。BからAに情報を渡すために必要な一時的な「パイプ」ファイルは1つだけです。これで、機能するパイプ、Aプロセスは「終了」メッセージをBに送信する必要はありません。Bプロセスは、ファイルの終わりに達するまでループするだけです。

VBSの適切なスリープ機能にアクセスできるのは確かに素晴らしいことです。これにより、readLine関数に短い遅延を簡単に導入して、CPUを中断させることができます。

ただし、readLIneには1つのしわがあります。最初は、readLineがstdinで利用可能な情報を検出し、Bが行の書き込みを完了する前にすぐに行を読み取ろうとすることに気付くまで、断続的な障害が発生していました。ファイルの終わりテストと読み取りの間に短い遅延を導入することで、問題を解決しました。 5ミリ秒の遅延でうまくいくように見えましたが、念のために2倍の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>

出力は、最後の「終了」行がないことを除いて、純粋なバッチソリューションの場合と同じです。

5
dbenham

注-振り返ってみると、質問をもう一度読んでも、これは求められていることを実行しません。それは2つのプロセスを一緒にリンクしますが(ネットワーク上でも機能する興味深い方法で!)、両方の方法をリンクしないためです。


私はあなたがこれに対するいくつかの答えを得ることを望みます。

これが私の答えですが、それを受け入れないでください、他の答えを待ってください、私は他のいくつかの答えを見たいと思っています。

これはcygwinから行われました。そして、「nc」コマンド(賢いコマンド)を使用します。 'wc-l'は行を数えるだけです。したがって、ncを使用して、2つのコマンド(この場合はechoとwc)をリンクしています。

左側のコマンドが最初に実行されました。

ncは、a)サーバーを作成する、またはb)rawモードのtelnetコマンドのようにサーバーに接続できるコマンドです。左のコマンドで「a」の使用法を使用し、右のコマンドで「b」の使用法を使用しています。

そこで、ncはそこに座って入力を待っていました。次に、その入力をwc -lにパイプして行数を数え、入力された行数を出力しました。

次に、その行を実行してテキストをエコーし​​、そのrawを前述のサーバーである127.0.0.1:123に送信しました。

enter image description here

Cygwinからnc.exeコマンドをコピーして、それと同じディレクトリにある必要なcygwin1.dllファイルを使用してみてください。または、私が持っているように、cygwin自体からそれを行うこともできます。 gnuwin32にnc.exeが表示されません。検索があります http://gnuwin32.sourceforge.net/ そしてncまたはnetcatは表示されません。しかし、cygwinを取得することはできます https://cygwin.com/install.html

4
barlop

1つのハック(私はこれをしたくないのですが、これは今のところ私が行っていることです)は、これを行うための 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"
2
DarthRubik