Bagaimana saya bisa menerapkan aliran data sirkuler di antara perintah yang saling berhubungan?

19

Saya tahu dua jenis bagaimana perintah dapat dihubungkan satu sama lain:

  1. dengan menggunakan Pipe (menempatkan std-output ke std-input dari perintah selanjutnya).
  2. dengan menggunakan Tee (splice output menjadi banyak output).

Saya tidak tahu apakah itu yang mungkin dilakukan, jadi saya menggambar tipe koneksi hipotetis:

masukkan deskripsi gambar di sini

Bagaimana mungkin untuk mengimplementasikan aliran sirkuler data di antara perintah seperti misalnya dalam kode pseudo ini, di mana saya menggunakan variabel alih-alih perintah .:

pseudo-code:

a = 1    # start condition 

repeat 
{
b = tripple(a)
c = sin(b) 
a = c + 1 
}
Abdul Al Hazred
sumber

Jawaban:

16

Lingkaran I / O Lingkaran Diimplementasikan dengan tail -f

Ini mengimplementasikan lingkaran I / O melingkar:

$ 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...]

Ini mengimplementasikan lingkaran input / output melingkar menggunakan algoritma sinus yang Anda sebutkan:

$ 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...]

Di sini, bcapakah matematika floating point dan s(...)notasi bc untuk fungsi sinus.

Implementasi Algoritma yang Sama Menggunakan Variabel Sebagai Gantinya

Untuk contoh matematika khusus ini, pendekatan I / O lingkaran tidak diperlukan. Seseorang dapat dengan mudah memperbarui suatu variabel:

$ 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...]
John1024
sumber
12

Anda dapat menggunakan FIFO untuk ini, dibuat dengan mkfifo. Perhatikan bahwa sangat mudah untuk secara tidak sengaja membuat jalan buntu. Biarkan saya jelaskan itu — ambil contoh "melingkar" hipotetis Anda. Anda memberi makan output perintah ke inputnya. Setidaknya ada dua cara kebuntuan ini:

  1. Perintah memiliki buffer output. Sebagian terisi, tetapi belum disiram (sebenarnya ditulis). Itu akan melakukannya begitu mengisi. Jadi ia kembali membaca inputnya. Itu akan duduk di sana selamanya, karena input yang ditunggu-tunggu sebenarnya ada di buffer output. Dan itu tidak akan memerah sampai mendapat input itu ...

  2. Perintah memiliki banyak output untuk ditulis. Itu mulai menulisnya, tetapi buffer pipa kernel terisi. Jadi ia duduk di sana, menunggu ruang di buffer. Itu akan terjadi segera setelah membaca inputnya, yaitu, tidak pernah seperti itu tidak akan melakukan itu sampai selesai menulis apa pun untuk outputnya.

Yang mengatakan, di sini adalah bagaimana Anda melakukannya. Contoh ini dengan od, untuk membuat rantai hex dumps yang tidak pernah berakhir:

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

Perhatikan bahwa akhirnya berhenti. Mengapa? Jalan buntu, # 2 di atas. Anda juga dapat melihat stdbufpanggilan di sana, untuk menonaktifkan buffering. Tanpa itu? Jalan buntu tanpa hasil.

derobert
sumber
terima kasih, saya tidak tahu apa-apa tentang buffer dalam konteks ini, apakah Anda tahu beberapa kata kunci untuk membaca lebih lanjut tentang itu?
Abdul Al Hazred
1
@AbdulAlHazred Untuk buffering input / output, lihat stdio buffering . Untuk buffer kernel dalam sebuah pipa, buffer pipa tampaknya berfungsi.
derobert
4

Secara umum saya akan menggunakan Makefile (perintah make) dan mencoba memetakan diagram Anda untuk membuat aturan file.

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

Untuk memiliki perintah berulang / siklik, kita perlu mendefinisikan kebijakan iterasi. Dengan:

SHELL=/bin/bash

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

accumulator:
    touch accumulator     #initial value

masing make- masing akan menghasilkan satu iterasi pada saat itu.

Joao
sumber
Penyalahgunaan lucu make, tetapi tidak perlu: Jika Anda menggunakan file perantara, mengapa tidak menggunakan loop untuk mengelolanya?
alexis
@ Alexis, makefiles mungkin berlebihan. Saya tidak begitu nyaman dengan loop: Saya kehilangan gagasan tentang jam, kondisi terputus-putus, atau contoh yang jelas. Diagram awal mengingat saya Alur kerja, dan tanda tangan fungsi. Untuk diagram yang kompleks, kita pada akhirnya membutuhkan konvensi data atau dan membuat aturan yang diketikkan. (Ini hanya intuisi kasar)
JJoao
@alexis, dan tentu saja, saya setuju dengan Anda.
JJoao
Saya tidak berpikir ini penyalahgunaan - makeini tentang makro yang merupakan aplikasi yang sempurna di sini.
mikeserv
1
@ mikeserv, Ya. Dan kita semua tahu bahwa alat pelecehan adalah Magna Carta bawah tanah Unix :)
JJoao
4

Anda tahu, saya tidak yakin Anda tentu membutuhkan umpan balik berulang-ulang sebagai diagram Anda menggambarkan, begitu banyak seperti mungkin Anda bisa menggunakan persisten pipa antara coprocesses . Kemudian lagi, mungkin tidak ada terlalu banyak perbedaan - setelah Anda membuka garis pada suatu proses, Anda dapat menerapkan loop gaya khas hanya dengan menulis informasi dan membaca informasi darinya tanpa melakukan sesuatu yang sangat luar biasa.

Pertama-tama, akan terlihat bahwa itu bcadalah kandidat utama untuk proses-ulang untuk Anda. Di bcAnda dapat definefungsi yang dapat melakukan cukup banyak apa yang Anda minta di pseudocode Anda. Misalnya, beberapa fungsi yang sangat sederhana untuk melakukan ini dapat terlihat seperti:

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

... yang akan mencetak ...

b=3
c=.14112000805986722210
a=1.14112000805986722210

Tapi tentu saja, itu tidak bertahan lama . Segera setelah subkulit yang bertanggung jawab atas printfpipa berhenti (tepat setelah printfmenulis a()\nke pipa) pipa tersebut dirobohkan dan bcinput menutup dan juga berhenti. Itu hampir tidak berguna seperti yang seharusnya.

@derobert telah menyebutkan FIFO s seperti yang bisa didapat dengan membuat file pipa bernama dengan mkfifoutilitas. Ini pada dasarnya hanya pipa juga, kecuali kernel sistem menautkan entri sistem file ke kedua ujungnya. Ini sangat berguna, tetapi akan lebih baik jika Anda hanya dapat memiliki pipa tanpa risiko mengintai di sistem file.

Ketika itu terjadi, shell Anda sering melakukan ini. Jika Anda menggunakan shell yang menerapkan substitusi proses maka Anda memiliki cara yang sangat mudah untuk mendapatkan pipa yang tahan lama - dari jenis yang mungkin Anda tetapkan untuk proses latar belakang yang dengannya Anda dapat berkomunikasi.

Dalam bash, misalnya, Anda dapat melihat cara kerja proses substitusi:

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

Anda lihat itu benar-benar substitusi . Shell mengganti nilai selama ekspansi yang sesuai dengan jalur ke tautan ke pipa . Anda dapat memanfaatkan itu - Anda tidak perlu dibatasi untuk menggunakan pipa itu hanya untuk berkomunikasi dengan proses apa pun yang berjalan di dalam ()substitusi itu sendiri ...

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

... yang mencetak ...

hey cat here

Sekarang saya tahu bahwa shell yang berbeda melakukan hal coprocess dengan cara yang berbeda - dan bahwa ada sintaks khusus bashuntuk pengaturan satu (dan mungkin satu untuk zshjuga) - tetapi saya tidak tahu bagaimana hal-hal itu bekerja. Saya hanya tahu bahwa Anda dapat menggunakan sintaks di atas untuk melakukan hal yang hampir sama tanpa semua omong kosong di keduanya bashdan zsh- dan Anda dapat melakukan hal yang sangat mirip dashdan busybox ashuntuk mencapai tujuan yang sama dengan dokumen-dokumen di sini (karena dashdan busyboxlakukan di sini- dokumen dengan pipa daripada file temp seperti yang dilakukan dua lainnya) .

Jadi, ketika diterapkan ke 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="$!"

... itu bagian yang sulit. Dan ini adalah bagian yang menyenangkan ...

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

... yang mencetak ...

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

... dan masih berjalan ...

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

... yang hanya membuat saya nilai terakhir untuk bc's adaripada memanggil a()fungsi untuk kenaikan itu dan cetakan ...

.29566669586771958965

Ini akan terus berjalan, pada kenyataannya, sampai saya membunuhnya dan menghancurkan pipa IPC-nya ...

kill "$BCPID"; exec 3>&- 4>&-
unset BCPID BCIN BCOUT
mikeserv
sumber
1
Sangat menarik. Catatan dengan bash dan zsh baru-baru ini Anda tidak perlu menentukan deskriptor file, mis. eval "exec {BCOUT}<>"<(:) "{BCIN}<>"<(:)Berfungsi juga
Thor