如何实现像 $RANDOM 这样的“生成器”?

kjo*_*kjo 11 zsh ipc fifo

$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=$(( ...
Run Code Online (Sandbox Code Playgroud)

有没有其他方法可以在 zsh 中实现这种生成器类型的对象?


编辑:这不起作用:

#!/usr/bin/env zsh

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

如果我将上述内容放在脚本中并运行它,则输出很少是预期的单行

1
Run Code Online (Sandbox Code Playgroud)

相反,它通常由几个整数组成;例如

1
2
3
4
5
Run Code Online (Sandbox Code Playgroud)

产生的线数从一次运行到下一次运行不同。

EDIT2:正如 jimmij 所指出的,更改echo/bin/echo可以解决问题。

Sté*_*las 11

ksh93有通常用于这种事情的学科。使用zsh,您可以劫持动态命名目录功能

定义例如:

zsh_directory_name() {
  case $1 in
    (n)
      case $2 in
        (incr) reply=($((++incr)))
      esac
  esac
}
Run Code Online (Sandbox Code Playgroud)

然后你可以使用每次~[incr]增加$incr

$ echo ~[incr]
1
$ echo ~[incr] ~[incr]
2 3
Run Code Online (Sandbox Code Playgroud)

您的方法失败,因为 in head -1 /tmp/ints, head 打开fifo,读取一个完整的缓冲区,打印一行,然后关闭它。一旦关闭,写入端会看到一个破损的管道。

相反,您可以这样做:

$ 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
Run Code Online (Sandbox Code Playgroud)

在那里,我们让读取端在 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
Run Code Online (Sandbox Code Playgroud)

那个时候,我们为每个值实例化一个管道。这允许返回包含任意行数的数据。

但是,在这种情况下,一旦cat打开 fifo,echo循环就会被解除阻塞,因此echocat读取内容并关闭管道(导致 nextecho实例化新管道)时可以运行更多。

解决方法可能是添加一些延迟,例如通过运行echo@jimmij 建议的外部或添加 some sleep,但这仍然不是很健壮,或者您可以在每个 之后重新创建命名管道echo

while 
  mkfifo $fifo &&
  echo $((++incr)) > $fifo &&
  rm -f $fifo
do : nothing
done &
Run Code Online (Sandbox Code Playgroud)

这仍然会留下管道不存在的短窗口(在unlink()done byrmmknod()done by 之间mkfifo)导致cat失败,以及非常短的窗口,其中管道已被实例化但没有进程将再次写入它(在write()close()done by echo) 导致cat不返回任何内容,以及命名管道仍然存在的短窗口,但没有任何内容将其打开以进行写入(在close()done byechounlink()done by 之间rmcat将挂起。

您可以通过执行以下操作来删除其中一些窗口

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
) &
Run Code Online (Sandbox Code Playgroud)

这样,唯一的问题是如果您同时运行多个 cat(它们都在我们的写入循环准备打开它以进行写入之前打开 fifo)在这种情况下它们将共享echo输出。

我还建议不要在世界可写目录中创建固定名称、世界可读的 fifos(或任何与此相关的文件),/tmp除非它是向系统上的所有用户公开的服务。