Ale*_*ede 70 bash grep tail logfiles
我正在使用 tail -f 来监视正在积极写入的日志文件。当某个字符串被写入日志文件时,我想退出监控,并继续我的脚本的其余部分。
目前我正在使用:
tail -f logfile.log | grep -m 1 "Server Started"
Run Code Online (Sandbox Code Playgroud)
找到字符串后,grep 按预期退出,但我需要找到一种方法使 tail 命令也退出,以便脚本可以继续。
小智 65
接受的答案对我不起作用,而且令人困惑并且会更改日志文件。
我正在使用这样的东西:
tail -f logfile.log | while read LOGLINE
do
[[ "${LOGLINE}" == *"Server Started"* ]] && pkill -P $$ tail
done
Run Code Online (Sandbox Code Playgroud)
如果日志行与模式匹配,则tail
终止此脚本启动的。
注意:如果您还想在屏幕上查看输出| tee /dev/tty
,请在 while 循环中测试之前回显或回显该行。
00p*_*eus 59
这是一个简单的单线。它不需要特定于 bash 或非 POSIX 的技巧,甚至不需要命名管道。您真正需要的只是将tail
from的终止解耦grep
。这样,一旦grep
结束,即使tail
尚未结束,脚本也可以继续。所以这个简单的方法会让你到达那里:
( tail -f -n0 logfile.log & ) | grep -q "Server Started"
Run Code Online (Sandbox Code Playgroud)
grep
将阻塞直到找到字符串,然后它将退出。通过tail
从它自己的子 shell 运行,我们可以将它置于后台使其独立运行。同时,主 shell 可以在grep
退出后立即继续执行脚本。tail
将停留在其子 shell 中,直到下一行写入日志文件,然后退出(甚至可能在主脚本终止后)。重点是管道不再等待tail
终止,所以管道一退出就grep
退出。
一些小的调整:
tail
使其从日志文件的当前最后一行开始读取,以防日志文件中较早存在字符串。tail
-F 而不是 -f。它不是 POSIX,但tail
即使在等待时轮换日志,它也可以工作。grep
在第一次出现后退出,但不打印触发行。它也是 POSIX,而 -m1 不是。小智 20
如果您使用的是 Bash(至少,但它似乎不是由 POSIX 定义的,因此在某些 shell 中可能会丢失),您可以使用语法
grep -m 1 "Server Started" <(tail -f logfile.log)
Run Code Online (Sandbox Code Playgroud)
它的工作原理与已经提到的 FIFO 解决方案非常相似,但编写起来要简单得多。
Ric*_*sen 15
有几种方法可以tail
退出:
tail
写另一行您可以tail
在grep
找到匹配项并退出后立即强制写入另一行输出。这将导致tail
get a SIGPIPE
,导致它退出。一种方法是tail
在grep
退出后修改正在监视的文件。
下面是一些示例代码:
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
Run Code Online (Sandbox Code Playgroud)
在此示例中,在关闭其标准输出cat
之前不会退出grep
,因此tail
在grep
有机会关闭其标准输入之前不太可能写入管道。 cat
用于传播grep
未修改的标准输出。
这种方法比较简单,但有几个缺点:
grep
在关闭stdin之前关闭stdout,总会有一个竞争条件: grep
关闭stdout,触发cat
退出,触发echo
,触发tail
输出一行。如果此行发送到grep
之前grep
有机会关闭标准输入,tail
则在SIGPIPE
写入另一行之前不会获得。tail
- 它不适用于其他程序。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}"
Run Code Online (Sandbox Code Playgroud)
# optional
可以删除标有注释的行,程序仍然可以运行;tail
会一直徘徊,直到它读取另一行输入或被其他进程杀死。
这种方法的优点是:
tail
grep
(或您正在使用的任何替代命令)的返回值这种方法的缺点是复杂,尤其是管理 FIFO:您需要安全地生成一个临时文件名,并且您需要确保临时 FIFO 被删除,即使用户在中间按下 Ctrl-C剧本。这可以使用陷阱来完成。
tail
您可以通过向tail
管道发送类似的信号来退出管道阶段SIGTERM
。挑战在于可靠地知道代码中同一位置的两件事: tail
的 PID 和是否grep
已退出。
使用类似 的管道tail -f ... | grep ...
,很容易修改第一个管道阶段,tail
通过后台处理tail
和读取将PID保存在变量中$!
。修改第二个管道阶段以kill
在grep
退出时运行也很容易。问题是管道的两个阶段在单独的“执行环境”(POSIX 标准的术语)中运行,因此第二个管道阶段无法读取第一个管道阶段设置的任何变量。如果不使用 shell 变量,要么第二阶段必须以某种方式找出tail
的 PID,以便它可以tail
在grep
返回时终止,要么必须在grep
返回时以某种方式通知第一阶段。
第二阶段可以pgrep
用来获取tail
的 PID,但这将是不可靠的(您可能匹配错误的进程)并且不可移植(pgrep
POSIX 标准未指定)。
第一阶段可以通过管道将 PID 发送到第二阶段echo
,但该字符串将与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}"
Run Code Online (Sandbox Code Playgroud)
这种方法具有前一种方法的所有优点和缺点,只是它更复杂。
POSIX 允许完全缓冲 stdin 和 stdout 流,这意味着tail
的输出可能不会在grep
任意长的时间内被处理。在 GNU 系统上不应该有任何问题:GNUgrep
使用read()
,它避免了所有缓冲,并且 GNU在写入 stdout 时tail -f
会定期调用fflush()
。非 GNU 系统可能需要做一些特殊的事情来禁用或定期刷新缓冲区。
让我扩展@00prometheus 的答案(这是最好的答案)。
也许您应该使用超时而不是无限期地等待。
下面的 bash 函数将阻塞,直到出现给定的搜索词或达到给定的超时。
如果在超时内找到该字符串,则退出状态将为 0。
wait_str() {
local file="$1"; shift
local search_term="$1"; shift
local wait_time="${1:-5m}"; shift # 5 minutes as default timeout
(timeout $wait_time tail -F -n0 "$file" &) | grep -q "$search_term" && return 0
echo "Timeout of $wait_time reached. Unable to find '$search_term' in '$file'"
return 1
}
Run Code Online (Sandbox Code Playgroud)
也许在启动服务器后日志文件还不存在。在这种情况下,您应该在搜索字符串之前等待它出现:
wait_server() {
echo "Waiting for server..."
local server_log="$1"; shift
local wait_time="$1"; shift
wait_file "$server_log" 10 || { echo "Server log file missing: '$server_log'"; return 1; }
wait_str "$server_log" "Server Started" "$wait_time"
}
wait_file() {
local file="$1"; shift
local wait_seconds="${1:-10}"; shift # 10 seconds as default timeout
until test $((wait_seconds--)) -eq 0 -o -f "$file" ; do sleep 1; done
((++wait_seconds))
}
Run Code Online (Sandbox Code Playgroud)
使用方法如下:
wait_server "/var/log/server.log" 5m && \
echo -e "\n-------------------------- Server READY --------------------------\n"
Run Code Online (Sandbox Code Playgroud)
目前,正如给定的那样,tail -f
这里的所有解决方案都存在选择先前记录的“服务器启动”行的风险(在您的特定情况下,这可能是也可能不是问题,具体取决于记录的行数和日志文件轮换/截断)。
与其让事情过于复杂tail
,不如使用更智能的,就像bmike用 perl snippit 展示的那样。最简单的解决方案是retail
将正则表达式支持与启动和停止条件模式集成在一起:
retail -f -u "Server Started" server.log > /dev/null
Run Code Online (Sandbox Code Playgroud)
这将像正常一样跟随文件,tail -f
直到出现该字符串的第一个新实例,然后退出。(-u
在正常的“跟随”模式下,该选项不会在文件的最后 10 行中的现有行上触发。)
如果您使用 GNU tail
(来自coreutils),下一个最简单的选择是使用--pid
FIFO(命名管道):
mkfifo ${FIFO:=serverlog.fifo.$$}
grep -q -m 1 "Server Started" ${FIFO} &
tail -n 0 -f server.log --pid $! >> ${FIFO}
rm ${FIFO}
Run Code Online (Sandbox Code Playgroud)
使用 FIFO 是因为必须单独启动进程才能获得和传递 PID。FIFO 仍然面临同样的问题,即及时写入导致tail
接收SIGPIPE,使用该--pid
选项以便tail
在它注意到grep
终止时退出(通常用于监视写入器进程而不是读取器,但tail
不真的不在乎)。Option-n 0
与 with 一起使用,tail
以便旧行不会触发匹配。
最后,您可以使用有状态的 tail,这将存储当前文件偏移量,因此后续调用仅显示新行(它还处理文件轮换)。此示例使用旧的 FWTK retail
*:
retail "${LOGFILE:=server.log}" > /dev/null # skip over current content
while true; do
[ "${LOGFILE}" -nt ".${LOGFILE}.off" ] &&
retail "${LOGFILE}" | grep -q "Server Started" && break
sleep 2
done
Run Code Online (Sandbox Code Playgroud)
* 注意,同名,与上一个选项不同的程序。
将文件的时间戳与状态文件 ( .${LOGFILE}.off
)进行比较,而不是使用 CPU 占用循环,然后睡眠。-T
如果需要,使用“ ”来指定状态文件的位置,以上假设为当前目录。随意跳过该条件,或者在 Linux 上,您可以使用更高效的方法inotifywait
:
retail "${LOGFILE:=server.log}" > /dev/null
while true; do
inotifywait -qq "${LOGFILE}" &&
retail "${LOGFILE}" | grep -q "Server Started" && break
done
Run Code Online (Sandbox Code Playgroud)
所以在做了一些测试之后,我找到了一种快速的 1 行方式来完成这项工作。当 grep 退出时, tail -f 似乎会退出,但有一个问题。它似乎只有在打开和关闭文件时才会触发。我通过在 grep 找到匹配项时将空字符串附加到文件中来完成此操作。
tail -f logfile |grep -m 1 "Server Started" | xargs echo "" >> logfile \;
Run Code Online (Sandbox Code Playgroud)
我不确定为什么文件的打开/关闭会触发 tail 以意识到管道已关闭,所以我不会依赖这种行为。但它现在似乎有效。
它关闭的原因,请查看 -F 标志与 -f 标志。
归档时间: |
|
查看次数: |
122968 次 |
最近记录: |