Come implementare "generatori" come $ RANDOM?


10

La variabile speciale $RANDOMha un nuovo valore ogni volta che si accede. A questo proposito, ricorda gli oggetti "generatori" trovati in alcune lingue.

C'è un modo per implementare qualcosa del genere in zsh?

Ho provato a farlo con pipe denominate, ma non ho trovato un modo per estrarre gli oggetti dal FIFO in modo controllato senza uccidere il processo "generatore". Per esempio:

% 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=$(( ...

Esiste un altro modo per implementare un tale oggetto di tipo generatore in zsh?


EDIT: questo non funziona:

#!/usr/bin/env zsh

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

Se inserisco quanto sopra in uno script ed eseguo, l'output raramente è la riga singola prevista

1

piuttosto, di solito è composto da più numeri interi; per esempio

1
2
3
4
5

Il numero di linee prodotte varia da una corsa all'altra.

EDIT2: Come sottolineato da Jimmij, il passaggio echoa /bin/echosi occupa del problema.

Risposte:


10

ksh93ha discipline che sono in genere utilizzate per questo tipo di cose. Con zsh, è possibile dirottare la funzione di directory denominata dinamica :

Definire ad esempio:

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

E poi puoi usare ~[incr]per ottenere un incremento $incrogni volta:

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

Il tuo approccio fallisce perché in head -1 /tmp/ints, head apre il fifo, legge un buffer completo, stampa una riga e quindi lo chiude . Una volta chiuso, la fine della scrittura vede un tubo rotto.

Invece, potresti fare:

$ 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

Lì, lasciamo aperta la fine della lettura su fd 3 e readlegge un byte alla volta, non un buffer completo per essere sicuri di leggere esattamente una riga (fino al carattere di nuova riga).

Oppure potresti fare:

$ 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

Quella volta, istanziamo una pipe per ogni valore. Ciò consente di restituire dati contenenti qualsiasi numero arbitrario di righe.

Tuttavia, in quel caso, non appena si catapre il fifo, il echociclo e viene sbloccato, quindi è possibile eseguirne di più echo, nel momento in cui catlegge il contenuto e chiude la pipe (facendo sì che la prossima echoistanze di una nuova pipe).

Una soluzione potrebbe essere quella di aggiungere un po 'di ritardo, come ad esempio eseguendo un esterno echocome suggerito da @jimmij o aggiungerne un po' sleep, ma ciò non sarebbe ancora molto robusto, oppure potresti ricreare la pipa denominata dopo ciascuna echo:

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

Ciò lascia ancora brevi finestre in cui la pipe non esiste (tra il unlink()fatto da rme il mknod()fatto da mkfifo) causando il catfallimento, e finestre molto brevi in ​​cui la pipe è stata istanziata ma nessun processo scriverà mai più su di esso (tra il write()e il close()fatto da echo) causando catdi non restituire nulla, e brevi finestre in cui esiste ancora la named pipe ma nulla lo aprirà mai per la scrittura (tra il close()fatto da echoe il unlink()fatto da rm) dove si catbloccherà.

Puoi rimuovere alcune di quelle finestre facendolo come segue:

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
) &

In questo modo, l'unico problema è se esegui più gatti contemporaneamente (tutti aprono il quindicesimo prima che il nostro ciclo di scrittura sia pronto per aprirlo per la scrittura) nel qual caso condivideranno l' echooutput.

Vorrei anche sconsigliare la creazione di nomi fissi, file quindici leggibili in tutto il mondo (o qualsiasi file per quello che conta) in directory scrivibili in tutto il mondo come a /tmpmeno che non sia un servizio da esporre a tutti gli utenti del sistema.


Grazie. A meno che non abbia fatto un errore, l'ultima ricetta che dai non funziona sempre. Vedi il mio EDIT.
kjo,

1
@kjo Try command echoo /bin/echoinvece di built-in echo. Inoltre - si può fare questo comando un po 'più corta: repeat 999 /bin/echo $((++incr)) > /tmp/int &.
Jimmij,

1
@kjo, vedi modifica.
Stéphane Chazelas,

4

Se vuoi eseguire il codice ogni volta che viene letto il valore di una variabile, non puoi farlo all'interno di zsh stesso. La RANDOMvariabile (come altre variabili speciali simili) è codificata nel codice sorgente zsh. È tuttavia possibile definire variabili speciali simili scrivendo un modulo in C. Molti dei moduli standard definiscono variabili speciali.

È possibile utilizzare un coprocesso per creare un generatore.

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

Tuttavia, questo è piuttosto limitato perché puoi avere un solo coprocesso. Un altro modo per ottenere progressivamente l'output da un processo è reindirizzare da una sostituzione di processo .

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

Si noti che head -1non funziona qui, perché legge un intero buffer, stampa ciò che piace ed esce. I dati letti dalla pipe rimangono letti; questa è una proprietà intrinseca delle pipe (non è possibile reinserire i dati). Il readbuiltin evita questo problema leggendo un byte alla volta, il che gli permette di fermarsi non appena trova la prima riga ma è molto lento (ovviamente non importa se stai leggendo qualche centinaio di byte).


2
C'è solo un coprocesso alla volta in zsh? Sono sorpreso - non capita spesso di vedere un posto in cui bash è più flessibile. :)
Charles Duffy,

@CharlesDuffy, puoi avere più di un coprocesso in zsh . i coprocessi sono stati aggiunti solo di recente bash, vedere la sezione bash a quel link.
Stéphane Chazelas,

@ StéphaneChazelas Come interagisci con più di un coprocesso in zsh? ( coproccoprocessi, intendo, non quelli zpty)
Gilles 'SO- smetti di essere malvagio'

Allo stesso modo di con ksh come spiegato a quel link. coproc cmd1; exec 3>&p 4<&p; coproc cmd2 3>&- 4<&-...
Stéphane Chazelas,

1

Penso che lo farei con un segnale di qualche tipo.

(   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
)&

Funziona per me, comunque.


$ 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

Su una nota solo leggermente correlata, ecco qualcosa di strano che ho scoperto l'altro giorno:

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

Diventa anche più strano:

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

Cosa c'è di strano ?
Stéphane Chazelas,

@ StéphaneChazelas - mi è sembrato strano che i collegamenti si autoalimentassero. E così facilmente. Ho pensato che fosse strano. E fico. Ho anche pensato che ci sarebbe dovuto essere una sorta di limite di ricorsione della profondità - sembra che la shell lo avrebbe innescato - o ha effettivamente bisogno di fare 40 collegamenti in un singolo percorso?
Mikeserv,


@ StéphaneChazelas - Va bene. Ma forse bashil comportamento è cambiato? Penso che l'affermazione sul pwdnon controllare e riferirsi solo a $PWDnon sia corretta. mkdir /tmp/dir; cd $_; PS4='$OLDPWD, $PWD + '; set -x; OLDPWD=$OLDPWD PWD=$PWD command eval ' cd ..; cd ..; cd ~; pwd'; pwd; cd .; pwdpotrebbe mostrarti cosa intendo. È un problema che mi ha infastidito con questa ns()cosa.
Mikeserv,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.