Wie implementiere ich "Generatoren" wie $ RANDOM?

10

Die spezielle Variable $RANDOMhat bei jedem Zugriff einen neuen Wert. In dieser Hinsicht erinnert es an die "Generator" -Objekte, die in einigen Sprachen zu finden sind.

Gibt es eine Möglichkeit, so etwas in zu implementieren zsh?

Ich habe versucht, dies mit Named Pipes zu tun, aber ich habe keine Möglichkeit gefunden, Elemente auf kontrollierte Weise aus dem Fifo zu extrahieren, ohne den "Generator" -Prozess zu beenden. Beispielsweise:

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

Gibt es eine andere Möglichkeit, ein solches Objekt vom Typ Generator in zsh zu implementieren?


EDIT: Das funktioniert nicht:

#!/usr/bin/env zsh

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

Wenn ich das Obige in ein Skript einfüge und es ausführe, wird die Ausgabe selten die erwartete einzelne Zeile

1

Vielmehr besteht es normalerweise aus mehreren ganzen Zahlen; z.B

1
2
3
4
5

Die Anzahl der produzierten Linien variiert von Lauf zu Lauf.

EDIT2: Wie jimmij betonte, kümmert sich der Wechsel echozu /bin/echozu dem Problem.

kjo
quelle

Antworten:

10

ksh93hat Disziplinen, die typischerweise für solche Dinge verwendet werden. Mit zshkönnten Sie die kapern dynamische genannte Verzeichnis - Funktion :

Definieren Sie zum Beispiel:

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

Und dann können Sie verwenden ~[incr], um $incrjedes Mal ein Inkrement zu erhalten :

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

Ihr Ansatz schlägt fehl, weil in head -1 /tmp/ints, head das FIFO öffnet, einen vollständigen Puffer liest, eine Zeile druckt und sie dann schließt . Nach dem Schließen sieht das Schreibende ein gebrochenes Rohr.

Stattdessen können Sie entweder Folgendes tun:

$ 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

Dort lassen wir readdas Leseende auf fd 3 offen und lesen jeweils ein Byte, nicht einen vollen Puffer, um sicherzugehen, dass genau eine Zeile (bis zum Zeilenumbruchzeichen) gelesen wird.

Oder Sie könnten tun:

$ 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

Dieses Mal instanziieren wir für jeden Wert eine Pipe. Dies ermöglicht die Rückgabe von Daten, die eine beliebige Anzahl von Zeilen enthalten.

In diesem Fall wird jedoch catdie echound die Schleife entsperrt, sobald das FIFO geöffnet wird, sodass mehr ausgeführt echowerden kann, wenn catder Inhalt gelesen und die Pipe geschlossen wird (wodurch die nächste echoeine neue Pipe instanziiert).

Eine echoProblemumgehung könnte darin bestehen, eine Verzögerung hinzuzufügen, z. B. indem Sie eine externe Datei ausführen , wie von @jimmij vorgeschlagen, oder eine hinzufügen sleep, aber das wäre immer noch nicht sehr robust, oder Sie könnten die benannte Pipe nach jeder neu erstellen echo:

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

Dadurch bleiben immer noch kurze Fenster, in denen die Pipe nicht vorhanden ist (zwischen " unlink()erledigt von" rmund " mknod()erledigt von" mkfifo), was catzu einem Fehler führt, und sehr kurze Fenster, in denen die Pipe instanziiert wurde, aber kein Prozess jemals wieder darauf schreibt (zwischen " write()und") close()erledigt durch echo) bewirkt cat, dass nichts zurückgegeben wird, und kurze Fenster, in denen die benannte Pipe noch vorhanden ist, aber nichts wird sie jemals zum Schreiben öffnen (zwischen close()erledigt von echound unlink()erledigt von rm), wo cathängen bleibt.

Sie können einige dieser Fenster folgendermaßen entfernen :

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

Auf diese Weise besteht das einzige Problem darin, dass Sie mehrere Katzen gleichzeitig ausführen (alle öffnen das FIFO, bevor unsere Schreibschleife zum Schreiben bereit ist). In diesem Fall teilen sie die echoAusgabe.

Ich würde auch davon abraten, einen festen Namen, weltweit lesbare Fifos (oder eine andere Datei für diese Angelegenheit) in weltweit beschreibbaren Verzeichnissen /tmpzu erstellen, es sei denn, es handelt sich um einen Dienst, der allen Benutzern im System zugänglich gemacht wird.

Stéphane Chazelas
quelle
Vielen Dank. Wenn ich keinen Fehler gemacht habe, funktioniert das letzte Rezept, das Sie geben, nicht immer. Siehe meine EDIT.
Kjo
1
@kjo Versuchen Sie es command echooder /bin/echostatt eingebaut echo. Außerdem können Sie diesen Befehl etwas kürzer machen : repeat 999 /bin/echo $((++incr)) > /tmp/int &.
Jimmyij
1
@kjo, siehe bearbeiten.
Stéphane Chazelas
4

Wenn Sie Code ausführen möchten, wenn der Wert einer Variablen gelesen wird, können Sie dies nicht in zsh selbst tun. Die RANDOMVariable ist (wie andere ähnliche spezielle Variablen) im zsh-Quellcode fest codiert. Sie können jedoch ähnliche Sondergrößen definieren , indem ein Modul in C. Viele der Standard - Schreibmodule definieren spezielle Variablen.

Sie können einen Coprozess verwenden , um einen Generator zu erstellen.

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

Dies ist jedoch ziemlich begrenzt, da Sie nur einen Coprozess haben können. Eine weitere Möglichkeit, schrittweise Ausgabe von einem Prozess zu erhalten , ist von einer Umleitung Prozess Substitution .

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

Beachten Sie, dass head -1dies hier nicht funktioniert, da ein ganzer Puffer gelesen, ausgedruckt und beendet wird. Die Daten, die aus der Pipe gelesen wurden, bleiben gelesen. Dies ist eine intrinsische Eigenschaft von Pipes (Sie können keine Daten wieder einfügen). Das integrierte readSystem vermeidet dieses Problem, indem es jeweils ein Byte liest. Dadurch kann es angehalten werden, sobald es die erste neue Zeile findet, ist jedoch sehr langsam (das spielt natürlich keine Rolle, wenn Sie nur ein paar hundert Bytes lesen).

Gilles 'SO - hör auf böse zu sein'
quelle
2
Es gibt jeweils nur einen Coprozess in zsh? Ich bin überrascht - es kommt nicht oft vor, dass ich einen Ort sehe, an dem Bash flexibler ist. :)
Charles Duffy
@CharlesDuffy, Sie können mehr als einen Coprozess in zsh haben . Coprozesse wurden erst vor kurzem hinzugefügt bash, siehe den Bash-Abschnitt unter diesem Link.
Stéphane Chazelas
@ StéphaneChazelas Wie interagierst du mit mehr als einem Coprozess in zsh? ( coprocCoprozesse, ich meine, keine leeren)
Gilles '
Genauso wie bei ksh, wie unter diesem Link erläutert. coproc cmd1; exec 3>&p 4<&p; coproc cmd2 3>&- 4<&-...
Stéphane Chazelas
1

Ich denke, ich würde es mit einem Signal tun.

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

Es funktioniert sowieso für mich.


$ 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

In einem nur geringfügig verwandten Punkt ist hier etwas Seltsames, das ich neulich entdeckt habe:

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

Es wird auch seltsamer:

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
quelle
Was ist daran komisch ?
Stéphane Chazelas
@ StéphaneChazelas - es schien nur seltsam, dass sich die Links selbst wiederholen würden. Und so leicht. Ich fand es komisch. Und cool. Ich dachte auch, dass es eine Art Tiefenrekursionsgrenze geben sollte - anscheinend würde die Shell dies auslösen - oder muss sie tatsächlich 40 Links in einem einzigen Pfad ausführen?
Mikeserv
@ StéphaneChazelas - Das ist gut. Aber vielleicht hat sich bashdas Verhalten geändert? Ich denke, die Aussage, pwdnicht nur zu überprüfen und sich darauf zu beziehen, $PWDist falsch. mkdir /tmp/dir; cd $_; PS4='$OLDPWD, $PWD + '; set -x; OLDPWD=$OLDPWD PWD=$PWD command eval ' cd ..; cd ..; cd ~; pwd'; pwd; cd .; pwdkönnte dir zeigen, was ich meine. Es ist ein Problem, das mich mit diesem ns()Ding nervte .
Mikeserv