有几种tail
退出方法:
可怜的方法:强迫tail
写另一行
找到匹配项并退出tail
后,可以立即强制写另一行输出grep
。这将导致tail
获得SIGPIPE
,导致退出。一种方法是tail
在grep
退出后修改被监视的文件。
这是一些示例代码:
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
在此示例中,在关闭其stdout cat
之前不会退出grep
,因此tail
在grep
有机会关闭其stdin 之前不太可能写入管道。 cat
用于传播grep
未修改的标准输出。
这种方法相对简单,但存在以下缺点:
- 如果
grep
在关闭stdin之前先关闭stdout,则总会出现竞争状况: grep
关闭stdout,触发cat
退出,触发echo
,触发tail
输出行。如果此行被发送到grep
之前grep
有机会关闭stdin,tail
则在SIGPIPE
写入另一行之前不会得到。
- 它需要对日志文件的写访问权。
- 您必须可以修改日志文件。
- 如果您恰巧与另一个进程同时写入,则可能会损坏日志文件(写入可能会交错,导致换行符出现在日志消息的中间)。
- 此方法特定于
tail
-它不能与其他程序一起使用。
- 第三管道阶段使得很难访问第二管道阶段的返回代码(除非您使用的是POSIX扩展名,例如
bash
的PIPESTATUS
数组)。在这种情况下这不是什么大问题,因为grep
它将始终返回0,但一般而言,中间阶段可能会被您关心其返回代码的其他命令所代替(例如,检测到“服务器启动”时返回0的东西,1当检测到“服务器启动失败”时)。
接下来的方法可以避免这些限制。
更好的方法:避免流水线
您可以使用FIFO来避免流水线,一旦grep
返回就允许继续执行。例如:
fifo=/tmp/tmpfifo.$$
mkfifo "${fifo}" || exit 1
tail -f logfile.log >${fifo} &
tailpid=$! # optional
grep -m 1 "Server Started" "${fifo}"
kill "${tailpid}" # optional
rm "${fifo}"
带有注释的行# optional
可以删除,程序仍然可以运行。tail
会一直持续到它读取另一行输入或被其他进程杀死为止。
这种方法的优点是:
- 您不需要修改日志文件
- 该方法除了适用于其他实用程序
tail
- 它没有比赛条件
- 您可以轻松获得
grep
(或您正在使用的任何替代命令)的返回值
这种方法的缺点是复杂性,尤其是管理FIFO:您需要安全地生成一个临时文件名,并且即使用户在中间按Ctrl-C,也需要确保删除该临时FIFO。剧本。这可以使用陷阱来完成。
替代方法:发送消息杀死 tail
您可以tail
通过发送信号来使管道阶段退出SIGTERM
。面临的挑战是可靠地知道代码中同一位置的两件事: tail
PID和是否grep
退出。
使用类似tail -f ... | grep ...
这样的管道,可以很容易地修改第一个管道阶段,以tail
通过后台tail
和读取将PID 保存在变量中$!
。修改第二个管道阶段以kill
在grep
退出时运行也很容易。问题在于流水线的两个阶段在单独的“执行环境”(以POSIX标准的术语)中运行,因此第二个流水线阶段无法读取第一个流水线阶段设置的任何变量。在不使用外壳变量的情况下,第二阶段必须以某种方式找出tail
PID,以便它可以tail
在grep
返回时终止,或者必须以某种方式在grep
返回时通知第一阶段。
第二阶段可用于pgrep
获取tail
的PID,但这将是不可靠的(您可能匹配错误的过程)并且不可移植(pgrep
POSIX标准未指定)。
第一级可以通过echo
输入PID 通过管道将PID发送到第二级,但是此字符串将与tail
的输出混合。根据的输出,对两者进行解复用可能需要复杂的转义方案tail
。
您可以使用FIFO使第二个管道阶段在grep
退出时通知第一个管道阶段。然后第一阶段就可以杀死tail
。这是一些示例代码:
fifo=/tmp/notifyfifo.$$
mkfifo "${fifo}" || exit 1
{
# run tail in the background so that the shell can
# kill tail when notified that grep has exited
tail -f logfile.log &
# remember tail's PID
tailpid=$!
# wait for notification that grep has exited
read foo <${fifo}
# grep has exited, time to go
kill "${tailpid}"
} | {
grep -m 1 "Server Started"
# notify the first pipeline stage that grep is done
echo >${fifo}
}
# clean up
rm "${fifo}"
除了更复杂之外,此方法具有以前方法的优点和缺点。
关于缓冲的警告
POSIX允许对stdin和stdout流进行完全缓冲,这意味着tail
的输出可能不会在grep
任意长时间内被处理。在GNU系统上应该没有任何问题:GNU grep
使用read()
,可以避免所有缓冲,并且GNU 在写入stdout时tail -f
会定期进行调用fflush()
。非GNU系统可能必须执行一些特殊的操作才能禁用或定期刷新缓冲区。