Jak wdrożyć „generatory”, takie jak $ RANDOM?

10

Zmienna specjalna $RANDOMma nową wartość za każdym razem, gdy jest dostępna. Pod tym względem przypomina obiekty „generatora” występujące w niektórych językach.

Czy istnieje sposób na wdrożenie czegoś takiego zsh?

Próbowałem to zrobić za pomocą nazwanych potoków, ale nie znalazłem sposobu, aby wyodrębnić przedmioty z fifo w kontrolowany sposób bez zabicia procesu „generatora”. Na przykład:

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

Czy istnieje jakiś inny sposób zaimplementowania takiego obiektu typu generatora w Zsh?


EDYCJA: To nie działa:

#!/usr/bin/env zsh

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

Jeśli wstawię powyższe do skryptu i uruchomię go, dane wyjściowe rzadko będą oczekiwaną pojedynczą linią

1

raczej składa się zwykle z kilku liczb całkowitych; na przykład

1
2
3
4
5

Liczba wyprodukowanych linii różni się w zależności od serii.

EDIT2: Jak jimmij wskazał, zmieniając echosię /bin/echodba o problemie.

kjo
źródło

Odpowiedzi:

10

ksh93ma dyscypliny, które są zwykle używane do tego rodzaju rzeczy. Dzięki zsh, można porwać katalogu dynamiczny o nazwie funkcji :

Zdefiniuj na przykład:

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

A następnie możesz użyć, ~[incr]aby uzyskać przyrost za $incrkażdym razem:

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

Twoje podejście kończy się niepowodzeniem, ponieważ w head -1 /tmp/ints, głowa otwiera fifo, czyta pełny bufor, drukuje jedną linię, a następnie ją zamyka . Po zamknięciu koniec pisania widzi zepsutą rurkę.

Zamiast tego możesz albo:

$ 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

Tam zostawiamy otwarty koniec odczytu na fd 3 i readodczytujemy jeden bajt na raz, a nie pełny bufor, aby upewnić się, że przeczytano dokładnie jedną linię (do znaku nowej linii).

Lub możesz zrobić:

$ 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

Tym razem tworzymy instancję potoku dla każdej wartości. Umożliwia to zwracanie danych zawierających dowolną liczbę wierszy.

Jednak w takim przypadku, gdy tylko catotwiera się fifo, echopętla i jest odblokowywana, aby echomożna było uruchomić więcej, zanim catodczyta zawartość i zamknie potok (powodując, że następny echoutworzy nowy potok).

Rozwiązaniem może być dodanie opóźnienia, na przykład poprzez uruchomienie zewnętrznego, echojak sugeruje @jimmij, lub dodanie go sleep, ale nadal nie byłoby to zbyt solidne, lub można odtworzyć nazwaną potok po każdym echo:

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

Że nadal pozostawia krótkie okna gdzie nie istnieją rura (między unlink()zrobione przez rmi mknod()wykonywane przez mkfifo) spowodowanie cat, by upaść, i bardzo krótkich okna gdzie rura została instancja ale nie proces będzie kiedykolwiek ponownie napisać do niego (między write()i close()zrobione przez echo) powodując, catże nic nie zwróci, i krótkie okna, w których nazwany potok nadal istnieje, ale nic nigdy go nie otworzy do zapisu (między close()wykonanym przez echoa unlink()wykonanym przez rm) gdzie catzawiesi się.

Możesz usunąć niektóre z tych okien , wykonując następujące czynności:

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

W ten sposób jedynym problemem jest uruchomienie kilku kotów jednocześnie (wszystkie otwierają fifo, zanim nasza pętla zapisu będzie gotowa do otwarcia go do zapisu), w którym to przypadku będą dzielić dane echowyjściowe.

Odradzałbym także tworzenie stałych nazw, czytelnych na całym świecie fifos (lub jakichkolwiek innych plików w tym zakresie) w katalogach zapisywalnych na świecie, takich jak /tmpchyba, że ​​jest to usługa dostępna dla wszystkich użytkowników w systemie.

Stéphane Chazelas
źródło
Dzięki. O ile nie popełniłem błędu, ostatni przepis, który podajesz, nie zawsze działa. Zobacz moją EDYCJĘ.
kjo
1
@kjo Spróbuj command echolub /bin/echozamiast wbudowanego echo. Także - można zrobić to polecenie trochę krótsza: repeat 999 /bin/echo $((++incr)) > /tmp/int &.
jimmij
1
@ kjo, patrz edycja.
Stéphane Chazelas
4

Jeśli chcesz wykonać kod za każdym razem, gdy odczytywana jest wartość zmiennej, nie możesz tego zrobić w samym zsh. RANDOMZmienna (jak innych podobnych zmiennych specjalnych) jest zakodowane w kodzie źródłowym zsh. Możesz jednak zdefiniować podobne zmienne specjalne, pisząc moduł w C. Wiele standardowych modułów definiuje zmienne specjalne.

Możesz użyć koprocesu, aby utworzyć generator.

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

Jest to jednak dość ograniczone, ponieważ możesz mieć tylko jeden koproces. Innym sposobem na stopniowe uzyskiwanie danych wyjściowych z procesu jest przekierowanie z substytucji procesu .

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

Zauważ, że head -1tutaj nie działa, ponieważ czyta cały bufor, drukuje to, co lubi i wychodzi. Dane odczytane z potoku pozostają odczytane; jest to nieodłączna właściwość potoków (nie można ponownie wprowadzać danych). readWbudowane unika tego problemu, czytając jeden bajt na raz, co pozwala na to, aby zatrzymać jak tylko znajdzie pierwszy znak nowej linii, ale jest bardzo powolny (oczywiście, że nie ma znaczenia, czy jesteś po prostu czytanie kilkaset bajtów).

Gilles „SO- przestań być zły”
źródło
2
W Zsh jest tylko jeden koproces na raz? Jestem zaskoczony - nie często widzę miejsce, w którym bash jest bardziej elastyczny. :)
Charles Duffy
@CharlesDuffy, możesz mieć więcej niż jeden koproces w Zsh . koprocesy zostały dodane bardzo niedawno bash, patrz sekcja bash pod tym linkiem.
Stéphane Chazelas,
@ StéphaneChazelas Jak współpracujesz z więcej niż jednym koprocessem w Zsh? ( coprockoprocesy, to znaczy nie zpty)
Gilles 'SO - przestań być zły'
Tak samo jak w przypadku ksh, jak wyjaśniono pod tym linkiem. coproc cmd1; exec 3>&p 4<&p; coproc cmd2 3>&- 4<&-...
Stéphane Chazelas
1

Myślę, że zrobiłbym to z jakimś sygnałem.

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

W każdym razie to dla mnie działa.


$ 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

Z niewielkiej uwagi, oto coś dziwnego, co odkryłem poprzedniego dnia:

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

Robi się też dziwniej:

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
źródło
Co w tym dziwnego ?
Stéphane Chazelas
@ StéphaneChazelas - wydawało się dziwne, że linki same się powtarzają. I tak łatwo. Myślałem, że to dziwne. I chłodny. Pomyślałem również, że powinien istnieć jakiś limit rekurencji głębokości - wydaje się, że powłoka je wyzwoli - czy faktycznie musi wykonać 40 linków na jednej ścieżce?
mikeserv
@ StéphaneChazelas - To dobrze. Ale może bashjest zachowanie się zmieniło? Myślę, że stwierdzenie o pwdnie sprawdzaniu i odwoływaniu się tylko do $PWDjest niepoprawne. mkdir /tmp/dir; cd $_; PS4='$OLDPWD, $PWD + '; set -x; OLDPWD=$OLDPWD PWD=$PWD command eval ' cd ..; cd ..; cd ~; pwd'; pwd; cd .; pwdmoże pokazać ci, co mam na myśli. To problem, który mnie zepsuł ns().
mikeserv