ทุกคนรู้วิธีที่จะทำให้ท่อทิศทางเดียวระหว่างสองโปรแกรม (ผูกstdout
หนึ่งครั้งแรกและครั้งที่สองอย่างใดอย่างหนึ่ง):stdin
first | second
แต่จะทำไพพ์สองทิศทางคือ cross-bind stdin
และstdout
สองโปรแกรมได้อย่างไร มีวิธีง่าย ๆ ในการทำเชลล์หรือไม่?
ทุกคนรู้วิธีที่จะทำให้ท่อทิศทางเดียวระหว่างสองโปรแกรม (ผูกstdout
หนึ่งครั้งแรกและครั้งที่สองอย่างใดอย่างหนึ่ง):stdin
first | second
แต่จะทำไพพ์สองทิศทางคือ cross-bind stdin
และstdout
สองโปรแกรมได้อย่างไร มีวิธีง่าย ๆ ในการทำเชลล์หรือไม่?
คำตอบ:
หากไพพ์ในระบบของคุณเป็นแบบสองทิศทาง (เช่นที่อยู่บน Solaris 11 และ BSD บางตัวเป็นอย่างน้อย แต่ไม่ใช่ Linux):
cmd1 <&1 | cmd2 >&0
ระวังการหยุดชะงักแม้ว่า
นอกจากนี้ทราบว่าบางรุ่น ksh93 ในบางระบบใช้ท่อ ( |
) โดยใช้คู่ซ็อกเก็ต ซ็อกเก็ตคู่เป็นแบบสองทิศทาง แต่ ksh93 ปิดทิศทางกลับอย่างชัดเจนดังนั้นคำสั่งข้างต้นจะไม่ทำงานกับ ksh93s เหล่านั้นแม้ในระบบที่ไพพ์ (ตามที่สร้างโดยการpipe(2)
เรียกของระบบ) เป็นแบบสองทิศทาง
ดีมันค่อนข้าง "ง่าย" ด้วยชื่อ pipes ( mkfifo
) ฉันใส่คำพูดง่าย ๆ เพราะถ้าโปรแกรมไม่ได้รับการออกแบบสำหรับสิ่งนี้
mkfifo fifo0 fifo1
( prog1 > fifo0 < fifo1 ) &
( prog2 > fifo1 < fifo0 ) &
( exec 30<fifo0 31<fifo1 ) # write can't open until there is a reader
# and vice versa if we did it the other way
ตอนนี้มีบัฟเฟอร์ปกติที่เกี่ยวข้องในการเขียน stdout ตัวอย่างเช่นหากทั้งสองโปรแกรมเป็น:
#!/usr/bin/perl
use 5.010;
say 1;
print while (<>);
คุณคาดหวังวงวนไม่สิ้นสุด แต่แทนที่จะทั้งสองจะหยุดชะงัก; คุณจะต้องเพิ่ม$| = 1
(หรือเทียบเท่า) เพื่อปิดบัฟเฟอร์การส่งออก การหยุดชะงักเกิดขึ้นเนื่องจากทั้งสองโปรแกรมกำลังรออะไรบางอย่างใน stdin แต่พวกเขาไม่เห็นมันเพราะมันนั่งอยู่ใน stdout buffer ของโปรแกรมอื่นและยังไม่ได้เขียนลงในไพพ์
อัพเดท : การรวมคำแนะนำจากStéphane Charzelas และ Joost:
mkfifo fifo0 fifo1
prog1 > fifo0 < fifo1 &
prog2 < fifo0 > fifo1
ทำเช่นเดียวกันสั้นกว่าและพกพาได้มากกว่า
prog1 < fifo | prog2 > fifo
.
prog1 < fifo | tee /dev/stderr | prog2 | tee /dev/stderr > fifo
คุณอาจจะสามารถทำมันได้กับสิ่งที่ต้องการ
prog2 < fifo0 > fifo1
คุณสามารถหลีกเลี่ยงการเต้นรำเล็ก ๆ น้อย ๆ ของคุณด้วยexec 30< ...
(ซึ่งโดยวิธีการทำงานร่วมกับbash
หรือyash
สำหรับ fds มากกว่า 10 เช่นนั้น)
dash
ดูเหมือนว่าตกลงเกินไป ( แต่มีพฤติกรรมแตกต่างกันเล็กน้อย)
ฉันไม่แน่ใจว่านี่คือสิ่งที่คุณพยายามทำ:
nc -l -p 8096 -c second &
nc -c first 127.0.0.1 8096 &
สิ่งนี้เริ่มต้นด้วยการเปิดซ็อกเก็ตการฟังบนพอร์ต 8096 และเมื่อมีการสร้างการเชื่อมต่อแล้วโปรแกรมจะวางไข่second
โดยstdin
ใช้เอาต์พุตสตรีมและstdout
เป็นสตรีมอินพุต
จากนั้นวินาทีnc
จะถูกเรียกใช้ซึ่งเชื่อมต่อกับพอร์ตฟังและวางไข่โปรแกรมfirst
ด้วยstdout
อินพุตสตรีมและstdin
เอาต์พุตสตรีม
สิ่งนี้ไม่ได้ใช้งานไปป์อย่างแน่นอน แต่ดูเหมือนจะทำสิ่งที่คุณต้องการ
เนื่องจากเป็นการใช้เครือข่ายจึงสามารถทำได้บนคอมพิวเตอร์ระยะไกล 2 เครื่อง นี่เกือบจะเป็นวิธีที่เว็บเซิร์ฟเวอร์ ( second
) และเว็บเบราว์เซอร์ ( first
) ทำงาน
nc -U
สำหรับซ็อกเก็ตโดเมน UNIX ที่ใช้พื้นที่ที่อยู่ของระบบไฟล์เท่านั้น
คุณสามารถใช้pipexec :
$ pipexec -- '[A' cmd1 ] '[B' cmd2 ] '{A:1>B:0}' '{B:1>A:0}'
bash
เวอร์ชัน 4 มีcoproc
คำสั่งที่อนุญาตให้ทำสิ่งนี้bash
โดยบริสุทธิ์โดยไม่มีไพพ์
coproc cmd1
eval "exec cmd2 <&${COPROC[0]} >&${COPROC[1]}"
กระสุนอื่น ๆ ก็สามารถทำได้coproc
เช่นกัน
ด้านล่างนี้เป็นคำตอบที่ละเอียดยิ่งขึ้น แต่เชื่อมโยงสามคำสั่งแทนที่จะเป็นสองคำสั่งซึ่งทำให้น่าสนใจยิ่งขึ้นเพียงเล็กน้อยเท่านั้น
หากคุณมีความสุขที่จะใช้cat
และstdbuf
จากนั้นสร้างสามารถทำให้เข้าใจง่ายขึ้น
เวอร์ชันที่ใช้bash
กับcat
และstdbuf
เข้าใจง่าย:
# start pipeline
coproc {
cmd1 | cmd2 | cmd3
}
# create command to reconnect STDOUT `cmd3` to STDIN of `cmd1`
endcmd="exec stdbuf -i0 -o0 /bin/cat <&${COPROC[0]} >&${COPROC[1]}"
# eval the command.
eval "${endcmd}"
หมายเหตุต้องใช้ eval เนื่องจากการขยายตัวแปรใน <& $ var นั้นผิดกฎหมายในเวอร์ชัน bash 4.2.25 ของฉัน
เวอร์ชันที่ใช้ pure bash
: แบ่งออกเป็นสองส่วนเปิดตัวไปป์ไลน์แรกภายใต้ coproc จากนั้นรับประทานอาหารกลางวันส่วนที่สอง (ทั้งคำสั่งเดียวหรือไปป์ไลน์) เชื่อมต่อไปยังส่วนแรก:
coproc {
cmd 1 | cmd2
}
endcmd="exec cmd3 <&${COPROC[0]} >&${COPROC[1]}"
eval "${endcmd}"
พิสูจน์แนวคิด:
ไฟล์./prog
เพียงหุ่นจำลองที่จะใช้ติดแท็กและพิมพ์ซ้ำบรรทัด การใช้ subshells เพื่อหลีกเลี่ยงปัญหาการบัฟเฟอร์อาจทำให้เกิด overkill ไม่ใช่จุดที่นี่
#!/bin/bash
let c=0
sleep 2
[ "$1" == "1" ] && ( echo start )
while : ; do
line=$( head -1 )
echo "$1:${c} ${line}" 1>&2
sleep 2
( echo "$1:${c} ${line}" )
let c++
[ $c -eq 3 ] && exit
done
ไฟล์./start_cat
นี้เป็นรุ่นที่ใช้bash
, cat
และstdbuf
#!/bin/bash
echo starting first cmd>&2
coproc {
stdbuf -i0 -o0 ./prog 1 \
| stdbuf -i0 -o0 ./prog 2 \
| stdbuf -i0 -o0 ./prog 3
}
echo "Delaying remainer" 1>&2
sleep 5
cmd="exec stdbuf -i0 -o0 /bin/cat <&${COPROC[0]} >&${COPROC[1]}"
echo "Running: ${cmd}" >&2
eval "${cmd}"
./start_part
หรือไฟล์ นี่เป็นรุ่นที่ใช้บริสุทธิ์bash
เท่านั้น สำหรับวัตถุประสงค์ในการสาธิตฉันยังใช้งานอยู่stdbuf
เนื่องจาก prog ที่แท้จริงของคุณจะต้องจัดการกับการบัฟเฟอร์ภายในเพื่อหลีกเลี่ยงการบล็อกเนื่องจากการบัฟเฟอร์
#!/bin/bash
echo starting first cmd>&2
coproc {
stdbuf -i0 -o0 ./prog 1 \
| stdbuf -i0 -o0 ./prog 2
}
echo "Delaying remainer" 1>&2
sleep 5
cmd="exec stdbuf -i0 -o0 ./prog 3 <&${COPROC[0]} >&${COPROC[1]}"
echo "Running: ${cmd}" >&2
eval "${cmd}"
เอาท์พุท:
> ~/iolooptest$ ./start_part
starting first cmd
Delaying remainer
2:0 start
Running: exec stdbuf -i0 -o0 ./prog 3 <&63 >&60
3:0 2:0 start
1:0 3:0 2:0 start
2:1 1:0 3:0 2:0 start
3:1 2:1 1:0 3:0 2:0 start
1:1 3:1 2:1 1:0 3:0 2:0 start
2:2 1:1 3:1 2:1 1:0 3:0 2:0 start
3:2 2:2 1:1 3:1 2:1 1:0 3:0 2:0 start
1:2 3:2 2:2 1:1 3:1 2:1 1:0 3:0 2:0 start
นั่นมัน
Building Block ที่สะดวกสำหรับการเขียนท่อสองทิศทางนั้นเป็นสิ่งที่เชื่อมต่อ stdout และ stdin ของกระบวนการปัจจุบันเข้าด้วยกัน เราเรียกมันว่า ioloop หลังจากเรียกใช้ฟังก์ชันนี้คุณจะต้องเริ่มต้นไปป์ปกติเท่านั้น:
ioloop && # stdout -> stdin
cmd1 | cmd2 # stdin -> cmd1 -> cmd2 -> stdout (-> back to stdin)
หากคุณไม่ต้องการแก้ไข descriptors ของเชลล์ระดับบนให้รันในเชลล์ย่อย:
( ioloop && cmd1 | cmd2 )
นี่คือการใช้งานแบบพกพาของ ioloop โดยใช้ไพพ์ที่มีชื่อ:
ioloop() {
FIFO=$(mktemp -u /tmp/ioloop_$$_XXXXXX ) &&
trap "rm -f $FIFO" EXIT &&
mkfifo $FIFO &&
( : <$FIFO & ) && # avoid deadlock on opening pipe
exec >$FIFO <$FIFO
}
ไพพ์ที่มีชื่อนั้นมีอยู่ในระบบไฟล์สั้น ๆ เท่านั้นระหว่างการติดตั้ง ioloop ฟังก์ชั่นนี้ค่อนข้างไม่ POSIX เพราะ mktemp เลิกใช้แล้ว (และอาจเสี่ยงต่อการถูกโจมตีจากการแข่งขัน)
การใช้งานเฉพาะ linux โดยใช้ / proc / เป็นไปได้ที่ไม่ต้องการไพพ์ที่มีชื่อ แต่ฉันคิดว่าอันนี้ดีเกินพอ
( : <$FIFO & )
ในรายละเอียดเพิ่มเติม ขอบคุณสำหรับการโพสต์
mktemp
ที่ไหน ฉันใช้มันอย่างกว้างขวางและหากมีเครื่องมือใหม่เข้ามาแทนที่ฉันต้องการเริ่มใช้งาน
นอกจากนี้ยังมี
dpipe
ที่ "ท่อสองทิศทาง" รวมอยู่ในแพคเกจ VDE2 และรวมอยู่ในปัจจุบัน distro ระบบการจัดการแพคเกจ
dpipe processA = processB
ดังนั้น , เครื่องมือเชื่อมต่อทุกอย่างเพื่อทุกอย่างเชื่อมต่อ.
socat EXEC:Program1 EXEC:Program2
ในฐานะที่เป็น @ StéphaneChazelasอย่างถูกต้องบันทึกในความคิดเห็นที่ตัวอย่างข้างต้นคือ "รูปแบบฐาน" เขามีตัวอย่างที่ดีกับตัวเลือกในคำตอบของเขาสำหรับคำถามที่คล้ายกัน
socat
ใช้ซ็อกเก็ตแทนไพพ์ (คุณสามารถเปลี่ยนได้ด้วยcommtype=pipes
) คุณอาจต้องการเพิ่มnofork
ตัวเลือกเพื่อหลีกเลี่ยงกระบวนการ socat พิเศษที่ผลักข้อมูลระหว่างท่อ / ซ็อกเก็ต (ขอบคุณสำหรับการแก้ไขคำตอบของฉัน btw)
มีคำตอบที่ยอดเยี่ยมมากมายที่นี่ ดังนั้นฉันแค่ต้องการเพิ่มบางสิ่งเพื่อให้ง่ายต่อการเล่นกับพวกเขา ฉันถือว่าstderr
ไม่ได้เปลี่ยนเส้นทางที่ใดก็ได้ สร้างสองสคริปต์ (สมมติว่า a.sh และ b.sh):
#!/bin/bash
echo "foo" # change to 'bar' in second file
for i in {1..10}; do
read input
echo ${input}
echo ${i} ${0} got: ${input} >&2
done
จากนั้นเมื่อคุณเชื่อมต่อกับวิธีที่ดีคุณควรเห็นบนคอนโซล:
1 ./a.sh got: bar
1 ./b.sh got: foo
2 ./a.sh got: foo
2 ./b.sh got: bar
3 ./a.sh got: bar
3 ./b.sh got: foo
4 ./a.sh got: foo
4 ./b.sh got: bar
...