Bagaimana cara mengimplementasikan "generator" seperti $ RANDOM?

10

Variabel khusus $RANDOMmemiliki nilai baru setiap kali diakses. Dalam hal ini, ini mengingatkan pada objek "generator" yang ditemukan dalam beberapa bahasa.

Apakah ada cara untuk mengimplementasikan sesuatu seperti ini zsh?

Saya mencoba melakukan ini dengan pipa bernama, tetapi saya tidak menemukan cara untuk mengekstrak item dari fifo secara terkendali tanpa membunuh proses "generator". Sebagai contoh:

% mkfifo /tmp/ints
% (index=0
   while ( true )
   do
       echo $index
       index=$(( index + 1 ))
   done) > /tmp/ints &
[1] 16309
% head -1 /tmp/ints
0
[1]  + broken pipe  ( index=0 ; while ( true; ); do; echo $index; index=$(( ...

Apakah ada cara lain untuk mengimplementasikan objek tipe generator di zsh?


EDIT: Ini tidak berfungsi:

#!/usr/bin/env zsh

FIFO=/tmp/fifo-$$
mkfifo $FIFO
INDEX=0
while true; do echo $(( ++INDEX )) > $FIFO; done &
cat $FIFO

Jika saya meletakkan di atas dalam skrip, dan menjalankannya, output jarang satu baris yang diharapkan

1

melainkan, biasanya terdiri dari beberapa bilangan bulat; misalnya

1
2
3
4
5

Jumlah garis yang dihasilkan bervariasi dari satu run ke yang berikutnya.

EDIT2: Seperti yang ditunjukkan Jimmij, berubah echountuk /bin/echomengurus masalah.

kjo
sumber

Jawaban:

10

ksh93memiliki disiplin ilmu yang biasanya digunakan untuk hal semacam ini. Dengan zsh, Anda dapat membajak fitur direktori bernama dinamis :

Tentukan misalnya:

zsh_directory_name() {
  case $1 in
    (n)
      case $2 in
        (incr) reply=($((++incr)))
      esac
  esac
}

Dan kemudian Anda dapat menggunakan ~[incr]untuk mendapatkan peningkatan $incrsetiap kali:

$ echo ~[incr]
1
$ echo ~[incr] ~[incr]
2 3

Pendekatan Anda gagal karena dalam head -1 /tmp/ints, kepala membuka fifo, membaca buffer penuh, mencetak satu baris, dan kemudian menutupnya . Setelah ditutup, ujung tulisan melihat pipa rusak.

Sebaliknya, Anda bisa melakukan:

$ fifo=~/.generators/incr
$ (umask  077 && mkdir -p $fifo:h && rm -f $fifo && mkfifo $fifo)
$ seq infinity > $fifo &
$ exec 3< $fifo
$ IFS= read -rneu3
1
$ IFS= read -rneu3
2

Di sana, kami membiarkan ujung bacaan terbuka pada fd 3, dan readmembaca satu byte pada satu waktu, bukan buffer penuh untuk memastikan untuk membaca tepat satu baris (hingga karakter baris baru).

Atau Anda bisa melakukannya:

$ fifo=~/.generators/incr
$ (umask  077 && mkdir -p $fifo:h && rm -f $fifo && mkfifo $fifo)
$ while true; do echo $((++incr)) > $fifo; done &
$ cat $fifo
1
$ cat $fifo
2

Waktu itu, kami instantiate pipa untuk setiap nilai. Itu memungkinkan pengembalian data yang berisi jumlah garis sembarang.

Namun, dalam kasus itu, segera setelah catmembuka fifo, echoloop dan tidak terbuka, sehingga lebih banyak yang echodapat dijalankan, pada saat catmembaca konten dan menutup pipa (menyebabkan berikutnya echoinstantiate pipa baru).

Pekerjaan di sekitar dapat menambahkan beberapa penundaan, seperti misalnya dengan menjalankan eksternal echoseperti yang disarankan oleh @jimmij atau menambahkan beberapa sleep, tapi itu masih belum terlalu kuat, atau Anda bisa membuat ulang pipa bernama setelah masing-masing echo:

while 
  mkfifo $fifo &&
  echo $((++incr)) > $fifo &&
  rm -f $fifo
do : nothing
done &

Itu masih menyisakan jendela pendek di mana pipa tidak ada (antara unlink()dilakukan oleh rmdan mknod()dilakukan oleh mkfifo) menyebabkan catkegagalan, dan jendela sangat pendek di mana pipa telah dipakai tetapi tidak ada proses akan pernah menulis lagi untuk itu (antara write()dan close()dilakukan oleh echo) menyebabkan cattidak menghasilkan apa-apa, dan jendela pendek di mana pipa bernama masih ada tetapi tidak akan pernah membukanya untuk menulis (antara close()dilakukan oleh echodan unlink()dilakukan oleh rm) di mana catakan menggantung.

Anda dapat menghapus beberapa jendela dengan melakukannya seperti:

fifo=~/.generators/incr
(
  umask  077
  mkdir -p $fifo:h && rm -f $fifo && mkfifo $fifo &&
  while
    mkfifo $fifo.new &&
    {
      mv $fifo.new $fifo &&
      echo $((++incr))
    } > $fifo
  do : nothing
  done
) &

Dengan begitu, satu-satunya masalah adalah jika Anda menjalankan beberapa kucing secara bersamaan (mereka semua membuka fifo sebelum loop penulisan kami siap membukanya untuk menulis) dalam hal ini mereka akan membagikan echohasilnya.

Saya juga menyarankan untuk tidak membuat nama tetap, fifos yang dapat dibaca dunia (atau file apa pun yang penting) di direktori dunia yang dapat ditulisi seperti /tmpkecuali jika itu adalah layanan yang akan diekspos kepada semua pengguna pada sistem.

Stéphane Chazelas
sumber
Terima kasih. Kecuali saya melakukan kesalahan, resep terakhir yang Anda berikan tidak selalu berhasil. Lihat EDIT saya.
kjo
1
@ kjo Coba command echoatau /bin/echobukannya bawaan echo. Juga - Anda dapat membuat perintah ini sedikit memagut pendek: repeat 999 /bin/echo $((++incr)) > /tmp/int &.
jimmij
1
@ kjo, lihat edit.
Stéphane Chazelas
4

Jika Anda ingin mengeksekusi kode setiap kali nilai variabel dibaca, Anda tidak bisa melakukannya di dalam zsh itu sendiri. The RANDOMvariabel (seperti variabel khusus lainnya yang sejenis) adalah keras-kode dalam kode sumber zsh. Namun Anda dapat mendefinisikan variabel khusus yang serupa dengan menulis modul dalam C. Banyak modul standar menentukan variabel khusus.

Anda dapat menggunakan coprocess untuk membuat generator.

coproc { i=0; while echo $i; do ((++i)); done }
for ((x=1; x<=3; x++)) { read -p n; echo $n; }

Namun ini sangat terbatas karena Anda hanya dapat memiliki satu proses. Cara lain untuk secara progresif mendapatkan output dari suatu proses adalah dengan mengalihkan dari substitusi proses .

exec 3< <(i=0; while echo $i; do ((++i)); done)
for ((x=1; x<=3; x++)) { read n <&3; echo $n; }

Catatan yang head -1tidak berfungsi di sini, karena membaca seluruh buffer, mencetak apa yang disukainya, dan keluar. Data yang telah dibaca dari pipa tetap dibaca; ini adalah properti intrinsik dari pipa (Anda tidak dapat memasukkan data kembali). The readbuiltin menghindari masalah ini dengan membaca satu byte pada suatu waktu, yang memungkinkan untuk berhenti secepat itu menemukan baris pertama, tetapi sangat lambat (tentu saja itu tidak masalah jika Anda hanya membaca beberapa ratus byte).

Gilles 'SANGAT berhenti menjadi jahat'
sumber
2
Hanya ada satu proses pada suatu waktu di zsh? Saya terkejut - tidak sering saya melihat tempat di mana bash lebih fleksibel. :)
Charles Duffy
@CharlesDuffy, Anda dapat memiliki lebih dari satu coprocess di zsh . coprocess hanya baru-baru ini ditambahkan ke bash, lihat bagian bash di tautan itu.
Stéphane Chazelas
@ StéphaneChazelas Bagaimana Anda berinteraksi dengan lebih dari satu coprocess di zsh? ( coprocProses ulang, maksud saya, bukan yang zpty)
Gilles 'SO- stop being evil'
Cara yang sama seperti dengan ksh seperti yang dijelaskan di tautan itu. coproc cmd1; exec 3>&p 4<&p; coproc cmd2 3>&- 4<&-...
Stéphane Chazelas
1

Saya pikir saya akan melakukannya dengan semacam sinyal.

(   trap   "read zero </tmp/ints" PIPE
    while  kill -s PIPE -0
    do     i=$zero
           while echo $((i++))
           do :; done 2>/dev/null >/tmp/ints
    done
)&

Itu bekerja untuk saya.


$ echo  15 >/tmp/ints; head -n 5 </tmp/ints
15
16
17
18
19
$ echo  75 >/tmp/ints; head -n 5 </tmp/ints
75
76
77
78
79

Pada catatan yang hanya sedikit berhubungan, ada sesuatu yang aneh yang saya temukan di hari lain:

mkdir nums; cd nums
for n in 0 1 2 3 4 5 6 7
do  ln -s ./ "$n"; done
echo [0-3]/*/*

0/0/0 0/0/1 0/0/2 0/0/3 0/0/4 0/0/5 0/0/6 0/0/7 0/1/0 0/1/1 0/1/2 0/1/3 0/1/4 0/1/5 0/1/6 0/1/7 0/2/0 0/2/1 0/2/2 0/2/3 0/2/4 0/2/5 0/2/6 0/2/7 0/3/0 0/3/1 0/3/2 0/3/3 0/3/4 0/3/5 0/3/6 0/3/7 0/4/0 0/4/1 0/4/2 0/4/3 0/4/4 0/4/5 0/4/6 0/4/7 0/5/0 0/5/1 0/5/2 0/5/3 0/5/4 0/5/5 0/5/6 0/5/7 0/6/0 0/6/1 0/6/2 0/6/3 0/6/4 0/6/5 0/6/6 0/6/7 0/7/0 0/7/1 0/7/2 0/7/3 0/7/4 0/7/5 0/7/6 0/7/7 1/0/0 1/0/1 1/0/2 1/0/3 1/0/4 1/0/5 1/0/6 1/0/7 1/1/0 1/1/1 1/1/2 1/1/3 1/1/4 1/1/5 1/1/6 1/1/7 1/2/0 1/2/1 1/2/2 1/2/3 1/2/4 1/2/5 1/2/6 1/2/7 1/3/0 1/3/1 1/3/2 1/3/3 1/3/4 1/3/5 1/3/6 1/3/7 1/4/0 1/4/1 1/4/2 1/4/3 1/4/4 1/4/5 1/4/6 1/4/7 1/5/0 1/5/1 1/5/2 1/5/3 1/5/4 1/5/5 1/5/6 1/5/7 1/6/0 1/6/1 1/6/2 1/6/3 1/6/4 1/6/5 1/6/6 1/6/7 1/7/0 1/7/1 1/7/2 1/7/3 1/7/4 1/7/5 1/7/6 1/7/7 2/0/0 2/0/1 2/0/2 2/0/3 2/0/4 2/0/5 2/0/6 2/0/7 2/1/0 2/1/1 2/1/2 2/1/3 2/1/4 2/1/5 2/1/6 2/1/7 2/2/0 2/2/1 2/2/2 2/2/3 2/2/4 2/2/5 2/2/6 2/2/7 2/3/0 2/3/1 2/3/2 2/3/3 2/3/4 2/3/5 2/3/6 2/3/7 2/4/0 2/4/1 2/4/2 2/4/3 2/4/4 2/4/5 2/4/6 2/4/7 2/5/0 2/5/1 2/5/2 2/5/3 2/5/4 2/5/5 2/5/6 2/5/7 2/6/0 2/6/1 2/6/2 2/6/3 2/6/4 2/6/5 2/6/6 2/6/7 2/7/0 2/7/1 2/7/2 2/7/3 2/7/4 2/7/5 2/7/6 2/7/7 3/0/0 3/0/1 3/0/2 3/0/3 3/0/4 3/0/5 3/0/6 3/0/7 3/1/0 3/1/1 3/1/2 3/1/3 3/1/4 3/1/5 3/1/6 3/1/7 3/2/0 3/2/1 3/2/2 3/2/3 3/2/4 3/2/5 3/2/6 3/2/7 3/3/0 3/3/1 3/3/2 3/3/3 3/3/4 3/3/5 3/3/6 3/3/7 3/4/0 3/4/1 3/4/2 3/4/3 3/4/4 3/4/5 3/4/6 3/4/7 3/5/0 3/5/1 3/5/2 3/5/3 3/5/4 3/5/5 3/5/6 3/5/7 3/6/0 3/6/1 3/6/2 3/6/3 3/6/4 3/6/5 3/6/6 3/6/7 3/7/0 3/7/1 3/7/2 3/7/3 3/7/4 3/7/5 3/7/6 3/7/7

Semakin aneh juga:

rm *
for a in  a b c d e f g h \
          i j k l m n o p \
          q r s t u v x y z
do 
    ln -s ./ "$a"
done
for a in *
do  echo "$a"/["$a"-z]
done

a/a a/b a/c a/d a/e a/f a/g a/h a/i a/j a/k a/l a/m a/n a/o a/p a/q a/r a/s a/t a/u a/v a/x a/y a/z
b/b b/c b/d b/e b/f b/g b/h b/i b/j b/k b/l b/m b/n b/o b/p b/q b/r b/s b/t b/u b/v b/x b/y b/z
c/c c/d c/e c/f c/g c/h c/i c/j c/k c/l c/m c/n c/o c/p c/q c/r c/s c/t c/u c/v c/x c/y c/z
d/d d/e d/f d/g d/h d/i d/j d/k d/l d/m d/n d/o d/p d/q d/r d/s d/t d/u d/v d/x d/y d/z
e/e e/f e/g e/h e/i e/j e/k e/l e/m e/n e/o e/p e/q e/r e/s e/t e/u e/v e/x e/y e/z
f/f f/g f/h f/i f/j f/k f/l f/m f/n f/o f/p f/q f/r f/s f/t f/u f/v f/x f/y f/z
g/g g/h g/i g/j g/k g/l g/m g/n g/o g/p g/q g/r g/s g/t g/u g/v g/x g/y g/z
h/h h/i h/j h/k h/l h/m h/n h/o h/p h/q h/r h/s h/t h/u h/v h/x h/y h/z
i/i i/j i/k i/l i/m i/n i/o i/p i/q i/r i/s i/t i/u i/v i/x i/y i/z
j/j j/k j/l j/m j/n j/o j/p j/q j/r j/s j/t j/u j/v j/x j/y j/z
k/k k/l k/m k/n k/o k/p k/q k/r k/s k/t k/u k/v k/x k/y k/z
l/l l/m l/n l/o l/p l/q l/r l/s l/t l/u l/v l/x l/y l/z
m/m m/n m/o m/p m/q m/r m/s m/t m/u m/v m/x m/y m/z
n/n n/o n/p n/q n/r n/s n/t n/u n/v n/x n/y n/z
o/o o/p o/q o/r o/s o/t o/u o/v o/x o/y o/z
p/p p/q p/r p/s p/t p/u p/v p/x p/y p/z
q/q q/r q/s q/t q/u q/v q/x q/y q/z
r/r r/s r/t r/u r/v r/x r/y r/z
s/s s/t s/u s/v s/x s/y s/z
t/t t/u t/v t/x t/y t/z
u/u u/v u/x u/y u/z
v/v v/x v/y v/z
x/x x/y x/z
y/y y/z
z/z
mikeserv
sumber
Apa yang aneh di dalamnya?
Stéphane Chazelas
@ StéphaneChazelas - sepertinya aneh bahwa tautannya akan muncul sendiri. Dan begitu mudah. Saya pikir itu aneh. Dan keren. Saya juga berpikir seharusnya ada semacam batas rekursi kedalaman - sepertinya shell akan memicu itu - atau apakah sebenarnya perlu melakukan 40 tautan dalam satu jalur?
mikeserv
@ StéphaneChazelas - Bagus. Tapi mungkin bashtingkah lakunya telah berubah? Saya pikir pernyataan tentang pwdtidak memeriksa dan merujuk hanya $PWDsalah. mkdir /tmp/dir; cd $_; PS4='$OLDPWD, $PWD + '; set -x; OLDPWD=$OLDPWD PWD=$PWD command eval ' cd ..; cd ..; cd ~; pwd'; pwd; cd .; pwdmungkin menunjukkan apa yang saya maksud. Ini masalah yang mengganggu saya dengan ns()hal ini .
mikeserv