สองโปรแกรมที่มี StdIn และ StdOut เชื่อมโยงกัน


12

สมมติว่าฉันมีสองโปรแกรมที่เรียกว่าและProgramA ProgramBฉันต้องการเรียกใช้ทั้งสองอย่างพร้อมกันในตัวแปลภาษา Windows cmd แต่ฉันต้องการStdOutของProgramAติดยาเสพติดกับStdInของProgramBและStdOutการProgramBติดยาเสพติดไปของStdInProgramA

บางสิ่งเช่นนี้

 ________________ ________________
| | | |
| StdIn (== ← === ← == (StdOut |
| โปรแกรม A | | โปรแกรม B |
| | | |
| StdOut) == → === → ==) StdIn |
| ________________ | | ________________ |

มีคำสั่งให้ทำเช่นนี้ - วิธีการบางอย่างเพื่อให้บรรลุหน้าที่นี้จาก cmd


4
ในยูนิกซ์ฉันใช้ชื่อ pipes เพื่อทำสิ่งนี้ windows มีสิ่งที่เรียกว่าชื่อ pipes ที่แตกต่างอย่างสิ้นเชิงและอาจใช้ไม่ได้
Jasen

@ Jasen ตามความคิดเห็นที่นี่linuxjournal.com/article/2156 ชื่อท่ออาจทำงานบน cygwin .. ถ้าเป็นเช่นนั้นคุณอาจจะทำอย่างละเอียดและโพสต์เป็นคำตอบ แต่ควรทดสอบกับ cygwin ก่อน
barlop

ฉันคิดว่าโปรแกรมจะต้องถูกเขียนเพื่อที่จะไม่วนซ้ำ .. ดังนั้นถ้า stdout เป็นบรรทัดว่างแล้วไม่ฟีดไปยัง stdin และถ้าไม่มีอะไรจะ stdin ออกแล้ว ดังนั้นการหลีกเลี่ยงการวนซ้ำไม่สิ้นสุด? แต่ที่น่าสนใจคือแอปพลิเคชันคืออะไร
barlop

2
บอกว่าคุณต้องการให้ "Eliza" มีการสนทนาสองครั้ง
Jasen

หากโปรแกรมไม่ได้รับการออกแบบอย่างรอบคอบเพื่อจัดการสิ่งเหล่านี้พวกเขาอาจหยุดชะงัก - ทั้งสองกำลังรอการป้อนข้อมูลจากอีกโปรแกรมหนึ่งและไม่มีอะไรเกิดขึ้น (หรือทั้งสองโปรแกรมกำลังพยายามเขียนลงในบัฟเฟอร์เต็มและไม่เคยอ่านอะไรเลย )
Random832

คำตอบ:


5

ไม่เพียง แต่จะสามารถทำได้มันสามารถทำได้โดยไม่ต้องทำอะไรเลยนอกจากเป็นไฟล์แบทช์! :-)

ปัญหาสามารถแก้ไขได้โดยใช้ไฟล์ชั่วคราวเป็น "ไพพ์" การสื่อสารสองทิศทางต้องการไฟล์ "pipe" สองไฟล์

กระบวนการ A อ่าน stdin จาก "pipe1" และเขียน stdout ไปที่ "pipe2"
กระบวนการ B อ่าน stdin จาก "pipe2" และเขียน stdout เป็น "pipe1"

เป็นสิ่งสำคัญที่ไฟล์ทั้งสองจะมีอยู่ก่อนที่จะเริ่มกระบวนการใดกระบวนการหนึ่ง ไฟล์ควรจะว่างเปล่าตั้งแต่เริ่มต้น

หากไฟล์แบตช์พยายามอ่านจากไฟล์ที่เกิดขึ้นที่ส่วนท้ายของปัจจุบันไฟล์นั้นจะไม่ส่งคืนอะไรเลยและไฟล์จะยังคงเปิดอยู่ ดังนั้นรูทีน readLine ของฉันจะอ่านอย่างต่อเนื่องจนกว่ามันจะได้ค่าที่ไม่ว่างเปล่า

ฉันต้องการอ่านและเขียนสตริงว่างดังนั้นรูทีน writeLine ของฉันจะต่อท้ายอักขระพิเศษที่ readLine ดึงออก

กระบวนการ A ของฉันควบคุมการไหล มันเริ่มต้นสิ่งต่าง ๆ โดยการเขียน 1 (ข้อความถึง B) จากนั้นเข้าสู่ลูปที่มีการวนซ้ำ 10 ครั้งที่มันอ่านค่า (ข้อความจาก B) เพิ่ม 1 และเขียนผลลัพธ์ (ข้อความถึง B) ในที่สุดมันจะรอข้อความสุดท้ายจาก B จากนั้นเขียนข้อความ "exit" ไปที่ B และออก

กระบวนการ B ของฉันอยู่ในวงวนไม่มีเงื่อนไขที่อ่านค่า (ข้อความจาก A) เพิ่ม 10 จากนั้นเขียนผลลัพธ์ (ข้อความถึง A) หาก B เคยอ่านข้อความ "เลิก" แล้วมันจะยุติลงทันที

ฉันต้องการแสดงให้เห็นว่าการสื่อสารนั้นเป็นแบบซิงโครนัสอย่างสมบูรณ์ดังนั้นฉันจึงแนะนำการหน่วงเวลาทั้งกระบวนการ A และ B

โปรดทราบว่าโพรซีเดอร์ readLine อยู่ในลูปที่ จำกัด ซึ่งใช้งาน CPU และระบบไฟล์อย่างต่อเนื่องในขณะที่รออินพุต การหน่วงเวลา PING สามารถเพิ่มลงในลูปได้ แต่จากนั้นกระบวนการจะไม่ตอบสนอง

ฉันใช้ไพพ์จริงเพื่อความสะดวกในการเปิดทั้งกระบวนการ A และ B แต่ไปป์นั้นใช้งานไม่ได้เพราะไม่มีการสื่อสารผ่านมัน การสื่อสารทั้งหมดผ่านไฟล์ "ไปป์" ชั่วคราวของฉัน

ฉันสามารถใช้ START / B เพื่อเริ่มกระบวนการได้เช่นกัน แต่ฉันต้องตรวจสอบเมื่อทั้งคู่ยุติเพื่อให้ฉันรู้ว่าเมื่อใดควรลบไฟล์ "ไพพ์" ชั่วคราว มันง่ายกว่ามากในการใช้ท่อ

ฉันเลือกที่จะใส่รหัสทั้งหมดในไฟล์เดียว - สคริปต์หลักที่เปิดตัว 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

--OUTPUT--

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 เนื่องจากตอนนี้เรามีไพพ์ที่ใช้งานได้แล้ว กระบวนการไม่จำเป็นต้องส่งข้อความ "ออกจากระบบ" ไปยัง B. กระบวนการ B จะวนซ้ำจนกว่าจะถึงจุดสิ้นสุดไฟล์

มันเป็นเรื่องดีที่มีการเข้าถึงฟังก์ชั่นการนอนหลับที่เหมาะสมใน VBS สิ่งนี้ช่วยให้ฉันสามารถแนะนำการหน่วงเวลาสั้น ๆ ในฟังก์ชั่น readLine เพื่อให้ CPU หยุดพักได้

อย่างไรก็ตามมีหนึ่งรอยย่นใน readLIne ในตอนแรกฉันได้รับความล้มเหลวเป็นระยะ ๆ จนกระทั่งฉันรู้ว่าบางครั้ง readLine จะตรวจสอบข้อมูลที่มีอยู่ใน stdin และจะพยายามอ่านบรรทัดทันทีก่อนที่ B จะมีโอกาสเขียนเส้นให้เสร็จ ฉันแก้ไขปัญหาด้วยการแนะนำการหน่วงเวลาสั้น ๆ ระหว่างการทดสอบสิ้นสุดไฟล์และการอ่าน การหน่วงเวลา 5 มิลลิวินาทีดูเหมือนจะเป็นการหลอกลวงสำหรับฉัน แต่ฉันเพิ่มเป็นสองเท่าถึง 10 มิลลิวินาทีเพื่อให้ปลอดภัย เป็นที่น่าสนใจมากว่าแบทช์ไม่ประสบปัญหานี้ เรากล่าวสั้น ๆ นี้ (5 โพสต์สั้น) ที่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>

เอาต์พุตเหมือนกันกับโซลูชันแบตช์บริสุทธิ์ยกเว้นบรรทัด "ออกจาก" ขั้นสุดท้ายจะไม่มี


4

หมายเหตุ - ในการหวนกลับให้อ่านคำถามอีกครั้งนี่ไม่ได้ทำในสิ่งที่ถูกถาม เนื่องจากในขณะที่มันเชื่อมโยงสองกระบวนการเข้าด้วยกัน (ในวิธีที่น่าสนใจที่จะทำงานผ่านเครือข่าย!) แต่ก็ไม่ได้เชื่อมโยงทั้งสองวิธี


ฉันหวังว่าคุณจะได้รับคำตอบจากเรื่องนี้

นี่คือคำตอบของฉัน แต่ไม่ยอมรับรอคำตอบอื่น ๆ ฉันกระตือรือร้นที่จะเห็นคำตอบอื่น ๆ

สิ่งนี้ทำจาก cygwin และใช้คำสั่ง 'nc' (อันชาญฉลาด) 'wc -l' เพียงนับจำนวนบรรทัด ดังนั้นฉันกำลังเชื่อมโยงสองคำสั่งในกรณีนี้ echo และ wc โดยใช้ nc

คำสั่งทางซ้ายทำก่อน

nc เป็นคำสั่งที่สามารถ a) สร้างเซิร์ฟเวอร์หรือ b) เชื่อมต่อเช่นคำสั่ง telnet ในโหมด raw ไปยังเซิร์ฟเวอร์ ฉันใช้การใช้ 'a' ในคำสั่งด้านซ้ายและการใช้ 'b' ในคำสั่งที่ถูกต้อง

ดังนั้น nc นั่งฟังการรอคอยอินพุตและจากนั้นไปป์ที่อินพุตนั้นwc -lและนับจำนวนบรรทัดแล้วเอาท์พุทจำนวนบรรทัดที่ป้อนเข้าไป

จากนั้นฉันก็วิ่งไปที่เส้นเพื่อสะท้อนข้อความและส่งข้อมูลดิบไปที่ 127.0.0.1:123 ซึ่งเป็นเซิร์ฟเวอร์ที่กล่าวถึง

ป้อนคำอธิบายรูปภาพที่นี่

คุณสามารถคัดลอกคำสั่ง nc.exe จาก cygwin และลองใช้มันและไฟล์ cygwin1.dll ที่ต้องการในไดเรกทอรีเดียวกัน หรือคุณสามารถทำได้จาก cygwin เองตามที่ฉันมี ฉันไม่เห็น nc.exe ใน gnuwin32 พวกเขามีการค้นหาhttp://gnuwin32.sourceforge.net/ และ nc หรือ netcat ไม่เกิดขึ้น แต่คุณสามารถรับ cygwin https://cygwin.com/install.html


ฉันใช้เวลาสิบครั้งในการอ่านสิ่งนี้ก่อนที่ฉันจะเข้าใจว่าเกิดอะไรขึ้น ..... ซึ่งค่อนข้างฉลาด
DarthRubik

@DarthRubik ใช่แล้วและคุณสามารถใช้netstat -aon | find ":123"เพื่อดูว่าคำสั่งทางด้านซ้ายสร้างเซิร์ฟเวอร์
barlop

แต่คุณจะสื่อสารทิศทางอื่นได้อย่างไร (เช่นจากwcด้านหลังไปยังechoคำสั่ง)
DarthRubik

เมื่อฉันลองด้วย 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"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.