ksh93
has disciplines which are typically used for this kind of thing. With zsh
, you could hijack the dynamic named directory feature:
Define for instance:
zsh_directory_name() {
case $1 in
(n)
case $2 in
(incr) reply=($((++incr)))
esac
esac
}
And then you can use ~[incr]
to get an incremented $incr
each time:
$ echo ~[incr]
1
$ echo ~[incr] ~[incr]
2 3
Your approach fails because in head -1 /tmp/ints
, head opens the fifo, reads a full buffer, prints one line, and then closes it. Once closed, the writing end sees a broken pipe.
Instead, you could either do:
$ 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
There, we leave the reading end open on fd 3, and read
reads one byte at a time, not a full buffer to be sure to read exactly one line (up to the newline character).
Or you could do:
$ 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
That time, we instantiate a pipe for every value. That allows returning data containing any arbitrary number of lines.
However, in that case, as soon as cat
opens the fifo, the echo
and the loop is unblocked, so more echo
could be run, by the time cat
reads the content and closes the pipe (causing the next echo
to instantiate a new pipe).
A work around could be to add some delay, like for instance by running an external echo
as suggested by @jimmij or add some sleep
, but that would still not be very robust, or you could recreate the named pipe after each echo
:
while
mkfifo $fifo &&
echo $((++incr)) > $fifo &&
rm -f $fifo
do : nothing
done &
That still leaves short windows where the pipe doesn't exist (between the unlink()
done by rm
and the mknod()
done by mkfifo
) causing cat
to fail, and very short windows where the pipe has been instantiated but no process will ever write again to it (between the write()
and the close()
done by echo
) causing cat
to return nothing, and short windows where the named pipe still exists but nothing will ever open it for writing (between the close()
done by echo
and the unlink()
done by rm
) where cat
will hang.
You could remove some of those windows by doing it like:
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
) &
That way, the only problem is if you run several cat at the same time (they all open the fifo before our writing loop is ready to open it for writing) in which case they will share the echo
output.
I would also advise against creating fixed name, world readable fifos (or any file for that matters) in world writable directories like /tmp
unless it's a service to be exposed to all users on the system.
command echo
or/bin/echo
instead of built-inecho
. Also - you can make this command a little bit shorter:repeat 999 /bin/echo $((++incr)) > /tmp/int &
. – jimmij Jan 13 '16 at 01:17