Як реалізувати "генератори" на зразок $ RANDOM?


10

Спеціальна змінна $RANDOMмає нове значення кожного разу, коли до неї звертаються. У цьому відношенні він нагадує об’єкти "генератора", які зустрічаються в деяких мовах.

Чи є спосіб реалізувати щось подібне zsh?

Я спробував це зробити з названими трубами, але не знайшов способу контрольовано витягти предмети з фіфо, не вбиваючи процес "генератора". Наприклад:

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

Чи є ще якийсь спосіб реалізувати такий об'єкт типу генератора в zsh?


EDIT: Це не працює:

#!/usr/bin/env zsh

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

Якщо я поставив вищезазначене в сценарій і запустив його, вихід рідко очікується одного рядка

1

швидше, вона складається з декількох цілих чисел; напр

1
2
3
4
5

Кількість вироблених рядків змінюється від одного циклу до другого.

EDIT2: Як вказував Джиммій, переходячи echoдо /bin/echoвирішення проблеми.

Відповіді:


10

ksh93має дисципліни, які зазвичай використовуються для подібних речей. З zsh, ви можете викрасти динамічну функцію з ім'ям каталогу :

Визначте, наприклад:

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

І тоді ви можете використовувати, ~[incr]щоб отримувати збільшення $incrкожного разу:

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

Ваш підхід не вдається, тому що в head -1 /tmp/intsголові відкривається фіфо, читається повний буфер, друкується один рядок, а потім закривається . Після закриття пишучий кінець бачить розбиту трубу.

Натомість ви можете:

$ 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

Там ми залишаємо кінець читання відкритим на fd 3 і readчитаємо по одному байту, а не повний буфер, щоб переконатися, що він читає рівно один рядок (до символу нового рядка).

Або ви могли б зробити:

$ 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

У цей час ми створюємо трубу для кожної цінності. Це дозволяє повертати дані, що містять будь-яку довільну кількість рядків.

Однак у цьому випадку, як тільки catвідкривається фіфа, echoцикл і цикл розблоковуються, тому echoможна буде запустити більше, до моменту catзчитування вмісту та закриття каналу (внаслідок чого наступна echoінстанціює нову трубку).

echoЗаохоченням може бути додавання деякої затримки, як, наприклад, запуск зовнішнього, як запропонував @jimmij, або додавання деяких sleep, але це все одно не буде дуже надійним, або ви можете відтворити названу трубу після кожного echo:

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

Це все ще залишає короткі вікна, де труба не існує (між unlink()виконаним rmі mknod()зробленим mkfifo), що призводить catдо виходу з ладу, і дуже короткі вікна, де труба була створена, але жоден процес ніколи не запишеться до неї (між write()і close()зроблено шляхом echo), що catне повертає нічого, і короткі вікна, де названа труба все ще існує, але нічого ніколи не відкриє її для написання (між close()виконаним echoі unlink()виконаним rm), де catбуде висіти.

Ви можете видалити деякі з цих вікон , зробивши це так:

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

Таким чином, єдина проблема полягає в тому, якщо ви запускаєте декілька котів одночасно (всі вони відкривають фіфо, перш ніж наша петля для письма буде готова відкрити її для запису), і в цьому випадку вони поділять echoрезультат.

Я б також радив не створювати фіксовану назву, доступний для читання у світі фіфос (або будь-який файл для цього) у світових каталогах, що записуються, наприклад, /tmpякщо це не сервіс, який піддається впливу всіх користувачів системи.


Дякую. Якщо я не помилився, останній рецепт, який ви даєте, не завжди працює. Дивіться мою редагування.
кьо

1
@kjo Спробуйте command echoабо /bin/echoзамість вбудованого echo. Крім того - ви можете зробити цю команду трохи коротше: repeat 999 /bin/echo $((++incr)) > /tmp/int &.
jimmij

1
@kjo, див. редагування.
Стефан Шазелас

4

Якщо ви хочете виконувати код щоразу, коли читається значення змінної, ви не можете цього робити всередині zsh. RANDOMЗмінної (як і інші подібні спеціальні змінні) жорстко закодовано в вихідному коді ЗШ. Однак ви можете визначити подібні спеціальні змінні, записавши модуль в C. Багато стандартних модулів визначають спеціальні змінні.

Ви можете використовувати копроцес для виготовлення генератора.

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

Однак це досить обмежено, оскільки у вас може бути лише один копроцес. Інший спосіб прогресивно отримувати вихід з процесу - це перенаправлення з заміни процесу .

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

Зауважте, що head -1це не працює, оскільки він читає цілий буфер, друкує те, що йому подобається, і виходить. Дані, які зчитуються з труби, залишаються прочитаними; це невід'ємна властивість труб (ви не можете вводити дані назад). readВбудована уникає цього питання, прочитавши один байт в той час, що дозволяє йому зупинитися , як тільки він знаходить перший символ нового рядка , але дуже повільно (звичайно , що не має значення , якщо ви просто читаєте кілька сотень байт).


2
У zsh одночасно є один копроцес? Я здивований - не часто я бачу місце, де бас гнучкіший. :)
Чарльз Даффі

@CharlesDuffy, ви можете мати більше одного копроцесу в zsh . coprocess були додані лише зовсім недавно bash, дивіться розділ bash за цим посиланням.
Стефан Шазелас

@ StéphaneChazelas Як ви взаємодієте з більш ніж одним копроцесом в zsh? ( coprocмаю на увазі, мабуть, не пусті)
перестань бути злим"

Так само, як і з ksh, як пояснено за цим посиланням. coproc cmd1; exec 3>&p 4<&p; coproc cmd2 3>&- 4<&-...
Стефан Шазелас

1

Я думаю, я зробив би це з сигналом якогось.

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

Це для мене все одно працює.


$ 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

Що стосується лише трохи пов’язаної ноти, ось щось дивне я виявив днями:

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

Це стає також дивнішим:

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

Що дивного в ньому?
Стефан Шазелас

@ StéphaneChazelas - просто здавалося дивним, що посилання самостійно повторюються. І так легко. Я думав, що це дивно. І круто. Я також подумав, що має бути якийсь обмеження рекурсії глибини - схоже, оболонка спровокує це - чи дійсно потрібно зробити 40 посилань за один шлях?
mikeserv


@ StéphaneChazelas - це добре. Але, можливо bash, поведінка змінилася? Я вважаю, що твердження про pwdнеперевірку та посилання лише на $PWDневірне. mkdir /tmp/dir; cd $_; PS4='$OLDPWD, $PWD + '; set -x; OLDPWD=$OLDPWD PWD=$PWD command eval ' cd ..; cd ..; cd ~; pwd'; pwd; cd .; pwdможе показати вам, що я маю на увазі. Це проблема, яка мене зачепила за цю ns()річ.
mikeserv
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.