我需要从每次程序运行时删除并重新创建的日志文件中提取信息。在(再次)检测到该文件存在后,我想将tail
其用于某个正则表达式。
正则表达式将匹配几次,但结果总是相同的,我想只打印一次,然后在重新创建文件时返回监视。
我研究了检测文件创建的方法。一种方法是通过inotifywait
,但这需要安装一个单独的包。
也许更简单的方法是利用stderr
当删除和创建被尾随的文件时尾部打印:
tail: '/path/to/debug.log' has become inaccessible: No such file or directory
tail: '/path/to/debug.log' has appeared; following new file
所以我申请了这个解决方案正在运行:
debug_file="/path/to/debug.log"
while true; do
# Monitor the log file until the 'new file' message appears
( tail -F $debug_file 2>&1 & ) | grep -q "has appeared; following new file"
# After the new file message appears, switch to monitoring for the regexp
tail -F "$debug_file" | while read -r line; do
id=$(echo "$line" | sed -n 's/.* etc \([0-9]\+\),.*/\1/p')
if [ -n "$id" ]; then
echo "ID: $id"
break # Exit the inner loop after the first match
fi
done
done
但我不喜欢这个解决方案启动两个不同的tail
进程。有没有一种方法可以达到相同的结果,但只使用 1 个tail
进程?
然后切换“模式”,首先查找文件创建,然后查找正则表达式,一旦找到,就返回“备用”模式,等待日志文件被删除并再次创建。
inotifywait 是一个更优雅的解决方案吗?理想情况下,我想要一个可以轻松移植到 Windows CMD 的解决方案。
答案1
在我看来,你不需要外循环。将尾部输出通过管道传输到 read 中可能允许您在一种状态机中进行操作,此时您处于两种状态。正常的尾部状态和新的文件状态,您等待下一个匹配来打印“ID:”行。
不过,我想您会想在阅读时尝试将 stderr 重定向到 stdout 。向您的示例添加了一些伪代码。
debug_file="/path/to/debug.log"
# After the new file message appears, switch to monitoring for the regexp
# state="new_file"
tail -F "$debug_file" 2>&1 | while read -r line; do
# if state = "new_file"
id=$(echo "$line" | sed -n 's/.* etc \([0-9]\+\),.*/\1/p')
if [ -n "$id" ]; then
echo "ID: $id"
# state = "id_printed"
fi
# else
# if line contains "has appeared; following new file"
# state = new_file
# echo "$line"
# fi
done
编辑。如果文件中的正则表达式可以有多个匹配项,并且您只想匹配第一个,则需要使用上述解决方案。否则,您可能会使用创建一个有点复杂的单行代码tail -F | grep | sed
(也可能使用 awk 而不是 sed)。根据您的要求,您甚至可以使用 -o 选项,仅使用 tail 和 grep 来完成任务。
例如尝试:tail -F /path/to/debug.log | grep -E '.* etc ([0-9]+),.*'
,然后看看你想如何从那里转换它。
答案2
使用TXR口齿不清:
(open-tail "/path/to/debug.log") ;; returns a stream that follows rotating log
您只需从该流中读取输入,例如使用以下内容:
(with-stream (s (open-tail "/path/to/debug.log"))
(whilet ((ln (get-line s)))
(if-match `@nil etc @id,@nil` ln
(put-line `ID: @id`))))
答案3
您需要在游戏中连续尾随文件并且仅输出唯一的行是:
tail -F "$debug_file" 2>/dev/null | awk '!seen[$0]++'
如果您只想考虑 sed 命令中的 regexpsed -n 's/.* etc \([0-9]\+\),.*/\1/p'
生成的部分行,您可以使用 GNU awk 的 3rg arg 来执行此操作(未经测试)match()
tail -F "$debug_file" 2>/dev/null |
awk 'match($0,/.* etc ([0-9]+),/,a) && !seen[a[1]]++ { print "ID: " a[1] }'
或者使用任何 awk:
tail -F "$debug_file" 2>/dev/null |
awk '/.* etc [0-9]+,/ && sub(/.* etc /,"") && sub(/,.*/,"") && !seen[$0]++ { print "ID: " $0 }'
如果您想丢弃在输入的上一次迭代中看到的任何“ID”,那么您可以这样做:
tail -F "$debug_file" 2>&1 |
awk '
/^tail: \047.*\047 has appeared; following new file$/ { delete seen }
... code from above ...
'
假设您要跟踪的文件本身不能包含与该^tail:.*
正则表达式匹配的行。
这很有可能是这样的:
awk '/.* etc [0-9]+,/ && sub(/.* etc /,"") && sub(/,.*/,"") && !seen[$0]++ { print "ID: " $0 }'
实际上可以写成更简洁的形式,例如:
awk '$(X-1) == "etc" && !seen[$X]++ { print "ID: " $X+0 }'
包含 ID 号的空格分隔字段号在哪里X
,但没有看到日志文件的示例,我不知道这是否有效,如果有效,X
会有什么值,也不确切情况会是什么样子。