Como implementar "geradores" como $ RANDOM?

10

A variável especial $RANDOMtem um novo valor toda vez que é acessada. A esse respeito, lembra os objetos "geradores" encontrados em alguns idiomas.

Existe uma maneira de implementar algo assim zsh?

Tentei fazer isso com pipes nomeados, mas não encontrei uma maneira de extrair itens do fifo de maneira controlada sem interromper o processo "gerador". Por exemplo:

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

Existe alguma outra maneira de implementar um objeto do tipo gerador no zsh?


EDIT: Isso não funciona:

#!/usr/bin/env zsh

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

Se eu colocar o acima em um script e executá-lo, a saída raramente será a única linha esperada

1

em vez disso, geralmente consiste em vários números inteiros; por exemplo

1
2
3
4
5

O número de linhas produzidas varia de uma execução para a seguinte.

EDIT2: Como jimmij apontou, mudar echopara /bin/echoresolve o problema.

kjo
fonte

Respostas:

10

ksh93tem disciplinas que normalmente são usadas para esse tipo de coisa. Com zsh, você pode seqüestrar o recurso de diretório dinâmico nomeado :

Defina, por exemplo:

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

E então você pode usar ~[incr]para obter um incremento a $incrcada vez:

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

Sua abordagem falha porque head -1 /tmp/ints, head abre o fifo, lê um buffer completo, imprime uma linha e depois a fecha . Uma vez fechado, o final da escrita vê um cano quebrado.

Em vez disso, você pode:

$ 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á, deixamos o final da leitura aberto no fd 3 e readlê um byte de cada vez, não um buffer completo, para ter certeza de ler exatamente uma linha (até o caractere de nova linha).

Ou você pode fazer:

$ 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

Nesse momento, instanciamos um canal para cada valor. Isso permite retornar dados contendo qualquer número arbitrário de linhas.

No entanto, nesse caso, assim que catabre o fifo, o echoloop é desbloqueado, para que mais echopossa ser executado, quando cato conteúdo é lido e fechado o pipe (fazendo com que o próximo echoinstancie um novo pipe).

Uma echosolução alternativa seria adicionar algum atraso, como por exemplo executando um externo como sugerido por @jimmij ou adicionar alguns sleep, mas isso ainda não seria muito robusto, ou você poderia recriar o canal nomeado após cada um echo:

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

Isso ainda deixa janelas curtas onde o canal não existe (entre o unlink()done by rme o mknod()done by mkfifo) causando catfalhas, e janelas muito curtas onde o pipe foi instanciado, mas nenhum processo será gravado novamente nele (entre o write()e o close()done by echo) fazendo catcom que não retorne nada e janelas curtas onde o pipe nomeado ainda existe, mas nada será aberto para gravação (entre o close()done by echoe o unlink()done by rm) em que catirá travar.

Você pode remover algumas dessas janelas fazendo o seguinte:

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

Dessa forma, o único problema é se você executar vários cat ao mesmo tempo (todos eles abrem o fifo antes que o nosso loop de gravação esteja pronto para abri-lo para gravação); nesse caso, eles compartilharão a echosaída.

Eu também desaconselharia a criação de nomes fixos, fifos legíveis pelo mundo (ou qualquer arquivo para isso) em diretórios graváveis ​​do mundo, a /tmpmenos que seja um serviço a ser exposto a todos os usuários no sistema.

Stéphane Chazelas
fonte
Obrigado. A menos que eu tenha cometido um erro, a última receita que você dá nem sempre funciona. Veja minha edição.
KJo
1
@kjo Tente command echoou em /bin/echovez de incorporado echo. Também - você pode fazer este comando um pouco mais curto: repeat 999 /bin/echo $((++incr)) > /tmp/int &.
jimmij
1
@kjo, veja editar.
Stéphane Chazelas
4

Se você deseja executar o código sempre que o valor de uma variável for lido, não poderá fazer isso dentro do próprio zsh. A RANDOMvariável (como outras variáveis ​​especiais semelhantes) é codificada no código-fonte zsh. No entanto, você pode definir variáveis ​​especiais semelhantes escrevendo um módulo em C. Muitos dos módulos padrão definem variáveis ​​especiais.

Você pode usar um co-processo para criar um gerador.

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

No entanto, isso é bastante limitado, porque você pode ter apenas um coprocesso. Outra maneira de obter progressivamente a saída de um processo é redirecionar de uma substituição de processo .

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

Observe que head -1não funciona aqui, porque lê um buffer inteiro, imprime o que gosta e sai. Os dados que foram lidos no canal permanecem lidos; essa é uma propriedade intrínseca dos pipes (você não pode inserir dados novamente). O readbuilt-in evita esse problema lendo um byte de cada vez, o que permite que ele pare assim que encontrar a primeira nova linha, mas é muito lento (é claro que isso não importa se você está lendo apenas algumas centenas de bytes).

Gilles 'SO- parar de ser mau'
fonte
2
Existe apenas um coprocesso por vez no zsh? Estou surpreso - não é sempre que vejo um lugar onde o bash é mais flexível. :)
Charles Duffy
@CharlesDuffy, você pode ter mais de um coprocesso no zsh . os coprocessos foram adicionados recentemente bash, consulte a seção bash nesse link.
Stéphane Chazelas
@ StéphaneChazelas Como você interage com mais de um co-processo no zsh? ( coprocCoprocesses, quero dizer, queridos não zpty)
Gilles 'SO parada sendo mal'
Da mesma maneira que com o ksh, conforme explicado nesse link. coproc cmd1; exec 3>&p 4<&p; coproc cmd2 3>&- 4<&-...
Stéphane Chazelas
1

Eu acho que faria isso com um sinal de algum 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
)&

Isso funciona para mim de qualquer maneira.


$ 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

Em uma nota apenas um pouco relacionada, aqui está algo estranho que eu descobri outro dia:

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

Também fica mais estranho:

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
fonte
O que há de estranho nisso?
Stéphane Chazelas
@ StéphaneChazelas - parecia estranho que os links se recuperassem automaticamente. E tão facilmente. Eu pensei que era estranho. E legal. Eu também pensei que deveria haver algum tipo de limite de recursão de profundidade - parece que o shell acionaria isso - ou ele realmente precisa fazer 40 links em um único caminho?
precisa saber é o seguinte
@ StéphaneChazelas - Isso é bom. Mas talvez basho comportamento tenha mudado? Eu acho que a afirmação de pwdnão verificar e se referir apenas a $PWDestá incorreta. mkdir /tmp/dir; cd $_; PS4='$OLDPWD, $PWD + '; set -x; OLDPWD=$OLDPWD PWD=$PWD command eval ' cd ..; cd ..; cd ~; pwd'; pwd; cd .; pwdpode mostrar o que eu quero dizer. É um problema que me incomodou com essa ns()coisa.
precisa saber é o seguinte