我使用创建了一个文件描述符
mkfifo fifo
一旦有东西写入这个管道,我就想重用它立即地。我应该使用
tail -f fifo
或者
while true; do cat fifo; done
?
他们似乎做同样的事情,我无法衡量性能上的差异。但是,当系统不支持inotify时(例如Busybox),则需要将前者设置为
tail -f -s 0 fifo
但这会占用 100% 使用率的 CPU(测试一下:mkfifo fifo && busybox tail -f -s 0 fifo & echo hi>fifo
/cancel withfg 1
和CtrlC)。那么 while-true-cat 是更可靠的解决方案吗?
答案1
当你这样做时:
cat fifo
假设还没有其他进程打开fifo
用于写入,cat
则会阻塞open()
系统调用。当另一个进程打开文件进行写入时,管道将被实例化并open()
返回。cat
将read()
循环调用并read()
阻塞,直到其他进程将数据写入管道。
cat
当所有其他写入进程都关闭其文件描述符时,将看到文件结束符 (eof) fifo
。此时cat
终止并且管道被破坏。
您需要cat
再次运行以读取之后写入的内容fifo
(但通过不同的管道实例)。
在:
tail -f file
就像cat
,tail
将等待进程打开文件进行写入。但在这里,由于你没有指定-n +1
从头开始复制,tail
所以需要等到 eof 才能找出最后 10 行是什么,所以直到写入结束关闭你才看到任何东西。
之后,tail
不会将其 fd 关闭到管道,这意味着管道实例不会被销毁,并且仍然会尝试每秒从管道中读取数据(在 Linux 上,可以通过使用inotify
某些版本来避免轮询) GNUtail
在那里做那件事)。这read()
将与 eof 返回(立即,这就是为什么你会看到 100% CPU -s 0
(对于 GNU 来说tail
意味着不在read()
s 之间等待而不是等待一秒钟)),直到其他进程再次打开该文件进行写入。
相反,在这里,您可能想要使用cat
,但要确保管道实例在实例化后始终保留在附近。为此,在大多数系统上,您可以执行以下操作:
cat 0<> fifo # the 0 is needed for recent versions of ksh93 where the
# default fd changed from 0 to 1 for the <> operator
cat
的 stdin 将开放用于读取和写入,这意味着cat
永远不会在其上看到 eof (即使没有其他进程打开该管道进行fifo
写入,它也会立即实例化管道)。
在这不起作用的系统上,您可以这样做:
cat < fifo 3> fifo
这样,一旦其他进程打开fifo
写入,第一个只读open()
将返回,此时 shell 将在open()
启动之前执行只写cat
,这将防止管道再次被破坏。
所以,总结一下:
- 相比之下
cat file
,第一轮之后就不会停止。 - 相比之下
tail -n +1 -f file
:在第一轮之后,它不会read()
每秒都做无用的事情,管道的一个实例上永远不会有 eof,当第二个进程在管道之后打开管道进行写入时,不会有长达一秒的延迟。第一个已经关闭了它。 - 相比
tail -f file
。除了上述内容之外,它不必等待第一轮结束就可以输出某些内容(仅输出最后 10 行)。 - 与循环相比
cat file
,只有一个管道实例。 1 中提到的竞争窗口将被避免。
1 此时,在最后一个read()
表示 eof 和cat
终止并关闭管道读取端之间,实际上有一个小窗口,在此期间进程可以fifo
再次打开管道进行写入(并且不会被阻止,因为仍然有一个读取端)。然后,如果它在cat
退出后且在另一个进程打开该进程fifo
进行读取之前写入了某些内容,则它将被 SIGPIPE 杀死。
答案2
让我提出另一个解决方案。只要有进程在第二端写入,管道就可供读取。所以你可以cat
在后台(或在另一个终端)创建一些假的,例如:
mkfifo fifo
cat >fifo &
cat fifo
现在,您可以根据需要写入 fifo,完成后只需使用 终止当前进程cat
,C-c然后首先从后台fg
启动,最后停止它。cat
C-d