$ RANDOMのような「ジェネレータ」を実装する方法は?


10

特殊変数$RANDOMは、アクセスされるたびに新しい値を持ちます。この点で、一部の言語にある「ジェネレーター」オブジェクトを連想させます。

このようなものを実装する方法はありzshますか?

名前付きパイプでこれを実行しようとしましたが、「ジェネレーター」プロセスを終了せずに制御された方法でfifoからアイテムを抽出する方法が見つかりませんでした。例えば:

% 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に実装する他の方法はありますか?


編集:これは機能しません:

#!/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:jimmijが指摘したように、に変更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、headがfifoを開き、バッファー全体を読み取り、1行を出力してから閉じるため、アプローチが失敗します。閉じられると、書き込み側には壊れたパイプが表示されます。

代わりに、次のいずれかを行うことができます。

$ 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バッファー全体ではなく、一度に1バイトを読み取り、正確に1行(改行文字まで)を読み取るようにします。

またはあなたがすることができます:

$ 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

そのとき、すべての値のパイプをインスタンス化します。これにより、任意の行数を含むデータを返すことができます。

ただし、その場合、catFIFO を開くとすぐに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
) &

このように、唯一の問題は、同時に複数の猫を実行する場合(書き込みループが書き込み用に開く準備ができる前に、すべてがfifoを開く場合)であり、その場合、それらはecho出力を共有します。

また/tmp、システム上のすべてのユーザーに公開されるサービスでない限り、世界中の書き込み可能なディレクトリに、固定名の世界中で読み取り可能なfifo(または問題のファイル)を作成しないこともお勧めします。


ありがとう。私が間違えない限り、あなたが与える最後のレシピはいつもうまくいくとは限りません。私の編集を参照してください。
kjo 2016年

1
@kjo組み込みcommand echoまたはの/bin/echo代わりに試してくださいecho。また、このコマンドを少し短くすることもできますrepeat 999 /bin/echo $((++incr)) > /tmp/int &
jimmij

1
@kjo、編集を参照してください。
ステファンChazelas

4

変数の値が読み取られるたびにコードを実行する場合、zsh自体の内部では実行できません。RANDOM(他の類似の特殊変数のような)変数は、ハードコードされたzshのソースコードです。ただし、Cでモジュールを記述することにより、同様の特殊変数を定義できます。標準モジュールの多くは、特殊変数を定義します。

コプロセスを使用してジェネレーターを作成できます。

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

ただし、コプロセスは1つしか持てないため、これはかなり制限されています。プロセスから出力を段階的に取得する別の方法は、プロセス置換からリダイレクトすることです。

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

head -1バッファ全体を読み取り、好きなものを出力して終了するため、ここでは機能しません。パイプから読み取られたデータは読み取られたままです。これはパイプの固有のプロパティです(データを元に戻すことはできません)。read組み込みは、それが最初の改行を見つけたが、非常に遅い(あなたはわずか数百のバイトを読んでいる場合は問題ではないもちろん)であるとして、それはすぐに停止することを可能にする、一度に1つのバイトを読み取ることによって、この問題を回避することができます。


2
zshでは一度に1つのコプロセスしかありませんか?私は驚いています-bashがより柔軟な場所を目にすることはあまりありません。:)
Charles Duffy、

@ CharlesDuffy、zshで複数のコプロセスを持つことができます。コプロセスはごく最近追加されましたbash。そのリンクのbashセクションを参照してください。
ステファンChazelas

@StéphaneChazelaszshで複数のコプロセスをどのように操作しますか?(coprocコプロセス、つまりzptyではありません)
Gilles「SO-邪悪なことをやめ

そのリンクで説明されているkshと同じ方法。coproc cmd1; exec 3>&p 4<&p; coproc cmd2 3>&- 4<&-...
ステファンChazelas

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

何がおかしいですか?
ステファンChazelas

@StéphaneChazelas-リンクが自己再帰するのは奇妙に思えました。そしてとても簡単。変だと思った。そしてかっこいい。私はまた、何らかの深さ再帰制限があるはずだと思っていました-シェルがそれをトリガーするようです-それとも実際には単一のパスで40リンクを実行する必要がありますか?
mikeserv 2016年


@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 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.