ฉันจะใช้การไหลเวียนของข้อมูลแบบวงกลมในคำสั่งที่เชื่อมต่อระหว่างกันได้อย่างไร


19

ฉันรู้ว่ามีสองประเภทวิธีที่คำสั่งสามารถเชื่อมต่อซึ่งกันและกัน:

  1. โดยใช้ Pipe (วาง std-output ลงใน std-input ของคำสั่งถัดไป)
  2. โดยใช้ Tee (แบ่งเอาต์พุตเป็นเอาต์พุตจำนวนมาก)

ฉันไม่ทราบว่านั่นคือทั้งหมดที่เป็นไปได้หรือไม่ดังนั้นฉันวาดประเภทการเชื่อมต่อสมมุติ:

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

เป็นไปได้อย่างไรที่จะใช้การไหลเวียนของข้อมูลระหว่างคำสั่งเช่นในรหัสเทียมนี้ซึ่งฉันใช้ตัวแปรแทนคำสั่ง:

pseudo-code:

a = 1    # start condition 

repeat 
{
b = tripple(a)
c = sin(b) 
a = c + 1 
}

คำตอบ:


16

วง I / O แบบวงนำไปใช้กับ tail -f

สิ่งนี้ใช้ลูป I / O แบบวงกลม:

$ echo 1 >file
$ tail -f file | while read n; do echo $((n+1)); sleep 1; done | tee -a file
2
3
4
5
6
7
[..snip...]

สิ่งนี้ใช้ลูปอินพุต / เอาท์พุตแบบวงกลมโดยใช้อัลกอริธึมไซน์ที่คุณพูดถึง:

$ echo 1 >file
$ tail -f file | while read n; do echo "1+s(3*$n)" | bc -l; sleep 1; done | tee -a file
1.14112000805986722210
.72194624281527439351
1.82812473159858353270
.28347272185896349481
1.75155632167982146959
[..snip...]

ที่นี่bcทำการคำนวณเลขทศนิยมและs(...)เป็นสัญลักษณ์ของ bc สำหรับฟังก์ชันไซน์

การใช้อัลกอริทึมเดียวกันโดยใช้ตัวแปรแทน

สำหรับตัวอย่างคณิตศาสตร์นี้โดยเฉพาะไม่จำเป็นต้องใช้วิธี I / O แบบวงกลม หนึ่งสามารถอัปเดตตัวแปร:

$ n=1; while true; do n=$(echo "1+s(3*$n)" | bc -l); echo $n; sleep 1; done
1.14112000805986722210
.72194624281527439351
1.82812473159858353270
.28347272185896349481
[..snip...]

12

คุณสามารถใช้ FIFO mkfifoนี้สร้างขึ้นด้วย อย่างไรก็ตามโปรดทราบว่ามันง่ายมากที่จะสร้างการหยุดชะงักโดยไม่ตั้งใจ ผมขออธิบายว่า - ยกตัวอย่าง "เวียน" สมมุติฐานของคุณ คุณป้อนผลลัพธ์ของคำสั่งไปยังอินพุต อย่างน้อยสองวิธีนี้อาจหยุดชะงัก:

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

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

ที่กล่าวว่านี่คือวิธีที่คุณทำ ตัวอย่างนี้มีodไว้เพื่อสร้างห่วงโซ่ที่ไม่มีที่สิ้นสุดของการทิ้งฐานสิบหก:

mkfifo fifo
( echo "we need enough to make it actually write a line out"; cat fifo ) \ 
    | stdbuf -i0 -o0 -- od -t x1 | tee fifo

โปรดทราบว่าในที่สุดก็หยุด ทำไม? มันหยุดชะงัก # 2 ข้างต้น นอกจากนี้คุณยังอาจสังเกตเห็นการstdbufโทรในนั้นเพื่อปิดการใช้งานการบัฟเฟอร์ ปราศจากมัน? การหยุดชะงักโดยไม่มีเอาต์พุตใด ๆ


ขอบคุณฉันไม่รู้เกี่ยวกับบัฟเฟอร์ในบริบทนี้คุณรู้จักคำสำคัญเพื่ออ่านเพิ่มเติมหรือไม่
Abdul Al Hazred

1
@AbdulAlHazred สำหรับบัฟเฟอร์ของอินพุต / เอาต์พุตมองขึ้นบัฟเฟอร์ stdio สำหรับบัฟเฟอร์ของเคอร์เนลในไพพ์ดูเหมือนว่าไพพ์บัฟเฟอร์ทำงานได้
Derobert

4

โดยทั่วไปแล้วฉันจะใช้ Makefile (command make) และลองแมปไดอะแกรมของคุณกับกฎของ makefile

f1 f2 : f0
      command < f0 > f1 2>f2

หากต้องการมีคำสั่งซ้ำ / เป็นรอบเราจำเป็นต้องกำหนดนโยบายการทำซ้ำ ด้วย:

SHELL=/bin/bash

a.out : accumulator
    cat accumulator <(date) > a.out
    cp a.out accumulator

accumulator:
    touch accumulator     #initial value

แต่ละคนmakeจะผลิตซ้ำหนึ่งครั้ง


การละเมิดที่น่ารักmakeแต่ไม่จำเป็น: หากคุณใช้ไฟล์ระดับกลางทำไมไม่ใช้การวนซ้ำเพื่อจัดการมัน
alexis

@alexis, makefiles น่าจะเป็น overkill ฉันไม่ค่อยสบายในเรื่องของลูป: ฉันคิดถึงความคิดของนาฬิกาสภาพการหยุดหรือเป็นตัวอย่างที่ชัดเจน ไดอะแกรมเริ่มต้นจดจำฉันถึงเวิร์กโฟลว์และลายเซ็นของฟังก์ชัน สำหรับไดอะแกรมที่ซับซ้อนเราจะต้องมีการเชื่อมต่อข้อมูลหรือและพิมพ์กฎของไฟล์ (นี่เป็นเพียงสัญชาตญาณที่ไม่เหมาะสม)
15:07 น. ของ JJoao

@alexis และแน่นอนฉันเห็นด้วยกับคุณ
JJoao

ฉันไม่คิดว่านี่เป็นการละเมิด - makeเป็นเรื่องเกี่ยวกับมาโครซึ่งเป็นแอปพลิเคชั่นที่สมบูรณ์แบบที่นี่
mikeserv

1
@mikeserv ใช่ และเราทุกคนรู้ว่าเครื่องมือที่ใช้ในทางที่ผิดคือ Magna Carta แห่ง Unix ใต้ดิน :)
JJoao

4

คุณรู้ว่าฉันไม่มั่นใจว่าคุณจำเป็นต้องมีห่วงข้อเสนอแนะซ้ำ ๆ เป็นแผนภาพภาพของคุณมากเท่าที่คุณสามารถใช้ ไพพ์ไลน์แบบถาวรระหว่างโพรเซส จากนั้นอีกครั้งอาจมีความแตกต่างไม่มากนัก - เมื่อคุณเปิดบรรทัดบนตัวประมวลผลร่วมคุณสามารถใช้ลูปลักษณะทั่วไปเพียงแค่เขียนข้อมูลลงไปและอ่านข้อมูลจากมันโดยไม่ต้องทำอะไรผิดไปมาก

ในสถานที่แรกมันจะปรากฏว่าbcเป็นผู้สมัครที่สำคัญสำหรับกระบวนการร่วมสำหรับคุณ ในbcคุณสามารถdefineทำหน้าที่ที่สามารถทำสิ่งที่คุณขอได้ใน pseudocode ของคุณ ตัวอย่างเช่นฟังก์ชั่นที่ง่ายมากที่จะทำสิ่งนี้อาจมีลักษณะ:

printf '%s()\n' b c a |
3<&0 <&- bc -l <<\IN <&3
a=1; b=0; c=0;
define a(){ "a="; return (a = c+1); }
define b(){ "b="; return (b = 3*a); }
define c(){ "c="; return (c = s(b)); }
IN

... ซึ่งจะพิมพ์ ...

b=3
c=.14112000805986722210
a=1.14112000805986722210

แต่แน่นอนมันไม่ได้สุดท้าย ทันทีที่ subshell รับผิดชอบการprintfหยุดทำงานของไพพ์(หลังจากprintfเขียนa()\nไปที่ไพพ์แล้ว) ไพพ์จะถูกดึงลงและbcอินพุตของปิดลงและมันก็จะหยุดทำงานเช่นกัน นั่นไม่ค่อยมีประโยชน์เท่าที่ควร

@derobert ได้พูดถึงFIFOแล้วว่าสามารถทำได้โดยการสร้างไฟล์ไปป์ที่มีชื่อพร้อมกับmkfifoยูทิลิตี้ สิ่งเหล่านี้เป็นเพียงท่อเช่นกันยกเว้นเคอร์เนลระบบเชื่อมโยงรายการระบบไฟล์ไปยังปลายทั้งสอง สิ่งเหล่านี้มีประโยชน์มาก แต่มันจะดีกว่าถ้าคุณมีท่อได้โดยไม่ต้องเสี่ยงว่ามันจะถูกสอดแนมในระบบไฟล์

เมื่อมันเกิดขึ้นเปลือกของคุณทำสิ่งนี้มากมาย หากคุณใช้เชลล์ที่ดำเนินการทดแทนกระบวนการคุณมีวิธีที่ง่ายมากในการได้รับไพพ์ที่คงทน - ซึ่งเป็นชนิดที่คุณอาจกำหนดให้กับกระบวนการแบ็กกราวด์ที่คุณสามารถสื่อสารได้

ใน bashตัวอย่างเช่นคุณสามารถดูวิธีการทำงานของกระบวนการเปลี่ยนตัว:

bash -cx ': <(:)'
+ : /dev/fd/63

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

bash -c '
    eval "exec 3<>"<(:) "4<>"<(:)
    cat  <&4 >&3  &
    echo hey cat >&4
    read hiback  <&3
    echo "$hiback" here'

... ที่พิมพ์ ...

hey cat here

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

ดังนั้นเมื่อนำไปใช้กับbc...

eval "exec 3<>"<(:) "4<>"<(:)
bc -l <<\INIT <&4 >&3 &
a=1; b=0; c=0;
define a(){ "a="; return (a = c+1); }
define b(){ "b="; return (b = 3*a); }
define c(){ "c="; return (c = s(b)); }
INIT
export BCOUT=3 BCIN=4 BCPID="$!"

... นั่นเป็นส่วนที่ยาก และนี่คือส่วนที่สนุก ...

set --
until [ "$#" -eq 10 ]
do    printf '%s()\n' b c a >&"$BCIN"
      set "$@" "$(head -n 3 <&"$BCOUT")"
done; printf %s\\n "$@"

... ที่พิมพ์ ...

b=3
c=.14112000805986722210
a=1.14112000805986722210
#...24 more lines...
b=3.92307618030433853649
c=-.70433330413228041035
a=.29566669586771958965

... และมันยังคงทำงานอยู่ ...

echo a >&"$BCIN"
read a <&"$BCOUT"
echo "$a"

... ซึ่งเพิ่งได้รับฉันค่าสุดท้ายสำหรับbc's aมากกว่าการเรียกa()ฟังก์ชั่นเพื่อเพิ่มมันและพิมพ์ ...

.29566669586771958965

ในความเป็นจริงมันจะยังคงทำงานต่อไปจนกว่าฉันจะฆ่ามันและทำลายท่อ IPC ของมัน ...

kill "$BCPID"; exec 3>&- 4>&-
unset BCPID BCIN BCOUT

1
น่าสนใจมาก. หมายเหตุด้วย bash และ zsh เมื่อเร็ว ๆ นี้คุณไม่จำเป็นต้องระบุ file descriptor เช่นeval "exec {BCOUT}<>"<(:) "{BCIN}<>"<(:)ทำงานได้ดี
Thor
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.