¿Cómo implementar "generadores" como $ RANDOM?

10

La variable especial $RANDOMtiene un nuevo valor cada vez que se accede. A este respecto, recuerda los objetos "generadores" que se encuentran en algunos idiomas.

¿Hay alguna manera de implementar algo como esto zsh?

Traté de hacer esto con tuberías con nombre, pero no encontré una manera de extraer elementos del fifo de manera controlada sin matar el proceso del "generador". Por ejemplo:

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

¿Hay alguna otra forma de implementar dicho objeto de tipo generador en zsh?


EDITAR: Esto no funciona:

#!/usr/bin/env zsh

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

Si pongo lo anterior en un script y lo ejecuto, el resultado rara vez es la línea única esperada

1

más bien, usualmente consiste en varios enteros; p.ej

1
2
3
4
5

El número de líneas producidas varía de una corrida a la siguiente.

EDIT2: Como señaló jimmij, cambiar echoa /bin/echose ocupa del problema.

kjo
fuente

Respuestas:

10

ksh93tiene disciplinas que generalmente se usan para este tipo de cosas. Con zsh, podría secuestrar la función de directorio dinámico con nombre :

Definir por ejemplo:

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

Y luego puede usar ~[incr]para obtener un incremento $incrcada vez:

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

Su enfoque falla porque en head -1 /tmp/ints, head abre el fifo, lee un búfer completo, imprime una línea y luego la cierra . Una vez cerrado, el final de la escritura ve una tubería rota.

En cambio, podrías hacer lo siguiente:

$ 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

Allí, dejamos abierto el final de lectura en fd 3 y readlee un byte a la vez, no un búfer completo para asegurarnos de leer exactamente una línea (hasta el carácter de nueva línea).

O podrías hacer:

$ 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

Esa vez, instanciamos una tubería para cada valor. Eso permite devolver datos que contienen cualquier número arbitrario de líneas.

Sin embargo, en ese caso, tan pronto como se catabre el fifo, echose desbloquea el bucle y, por lo que echopodría ejecutarse más, para cuando catlea el contenido y cierre la tubería (haciendo que la próxima echoinstancia una nueva tubería).

Una echosolución podría ser agregar algún retraso, como por ejemplo ejecutando un externo como lo sugiere @jimmij o agregar algunos sleep, pero eso aún no sería muy robusto, o podría recrear la tubería con nombre después de cada uno echo:

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

Eso todavía deja ventanas cortas donde la tubería no existe (entre el unlink()hecho por rmy el mknod()hecho por mkfifo) haciendo catque falle, y ventanas muy cortas donde la tubería ha sido instanciada pero ningún proceso volverá a escribirle (entre el write()y el close()hecho por echo) haciendo catque no se devuelva nada, y las ventanas cortas donde todavía existe la tubería nombrada pero nada la abrirá para escribir (entre close()hecho por echoy unlink()hecho por rm) donde catse bloqueará.

Puede eliminar algunas de esas ventanas haciéndolo como:

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

De esa manera, el único problema es si ejecuta varios cat al mismo tiempo (todos abren el fifo antes de que nuestro bucle de escritura esté listo para abrirlo para escribir) en cuyo caso compartirán la echosalida.

También recomendaría no crear nombres fijos, juegos legibles en todo el mundo (o cualquier archivo que importe) en directorios de escritura mundial, a /tmpmenos que sea un servicio que se exponga a todos los usuarios del sistema.

Stéphane Chazelas
fuente
Gracias. A menos que haya cometido un error, la última receta que da no siempre funciona. Ver mi EDITAR.
kjo
1
@kjo Probar command echoo en /bin/echolugar de integrado echo. También - usted puede hacer este comando un poco más corto: repeat 999 /bin/echo $((++incr)) > /tmp/int &.
jimmij
1
@kjo, ver editar.
Stéphane Chazelas
4

Si desea ejecutar código cada vez que se lee el valor de una variable, no puede hacerlo dentro de zsh. La RANDOMvariable (como otras variables especiales similares) está codificada en el código fuente zsh. Sin embargo, puede definir variables especiales similares escribiendo un módulo en C. Muchos de los módulos estándar definen variables especiales.

Puedes usar un coproceso para hacer un generador.

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

Sin embargo, esto es bastante limitado porque solo puede tener un coproceso. Otra forma de obtener progresivamente la salida de un proceso es redirigir desde una sustitución de proceso .

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

Tenga en cuenta que head -1no funciona aquí, porque lee un búfer completo, imprime lo que le gusta y sale. Los datos que se han leído de la tubería siguen siendo leídos; Esta es una propiedad intrínseca de las tuberías (no se pueden volver a introducir datos). El readbuiltin evita este problema al leer un byte a la vez, lo que le permite detenerse tan pronto como encuentra la primera línea nueva pero es muy lento (por supuesto, eso no importa si solo está leyendo unos pocos cientos de bytes).

Gilles 'SO- deja de ser malvado'
fuente
2
¿Solo hay un coproceso a la vez en zsh? Me sorprende: no es frecuente que vea un lugar donde bash sea más flexible. :)
Charles Duffy
@CharlesDuffy, puede tener más de un coproceso en zsh . los coprocesos solo se han agregado recientemente bash, consulte la sección bash en ese enlace.
Stéphane Chazelas
@ StéphaneChazelas ¿Cómo interactúa con más de un coproceso en zsh? ( coproccoprocesos, quiero decir, no zpty)
Gilles 'SO- deja de ser malvado' el
De la misma manera que con ksh como se explica en ese enlace. coproc cmd1; exec 3>&p 4<&p; coproc cmd2 3>&- 4<&-...
Stéphane Chazelas
1

Creo que lo haría con una señal de algún 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
)&

Funciona para mí de todos modos.


$ 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

En una nota ligeramente relacionada, aquí hay algo extraño que descubrí el otro día:

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

También se vuelve más extraño:

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
fuente
¿Qué tiene de raro ?
Stéphane Chazelas
@ StéphaneChazelas: parecía extraño que los enlaces se repitieran por sí mismos. Y muy fácilmente. Me pareció que era raro. Y fresco. También pensé que se suponía que debía haber algún tipo de límite de recursión de profundidad, parece que el shell activaría eso, ¿o realmente necesita hacer 40 enlaces en una sola ruta?
mikeserv
@ StéphaneChazelas - Eso está bien. Pero tal vez bashel comportamiento ha cambiado? Creo que la afirmación sobre pwdno verificar y referirse solo $PWDes incorrecta. mkdir /tmp/dir; cd $_; PS4='$OLDPWD, $PWD + '; set -x; OLDPWD=$OLDPWD PWD=$PWD command eval ' cd ..; cd ..; cd ~; pwd'; pwd; cd .; pwdpodría mostrarte lo que quiero decir. Es un problema que me molestó con esto ns().
mikeserv