我目前正在尝试构建一个脚本,该脚本从一个(或更好,多个)服务器日志中读取所有相关行并按顺序显示它们。我是 shell 脚本编写的新手,因此犯了很多错误,但现在我的脚本速度太慢,以至于无法正常工作。
这是我的问题。我首先通过错误代码识别一些记录。包含该错误代码的行是非结构化 xml。为了获取与该错误相关的其他行,我需要在 XML 中找到另一个包含 ID 的标签。该 ID 与另一行匹配,该行具有时间戳和关联的线程或任务号。通过这两个值,理论上我可以通过查找与我的错误消息“接近”的线程号的所有行来获取与我的错误相关的所有行。理论上,由于文件有 150MB 大。
因此,我构建了如下所示的代码:
grep -c
获取错误数量;grep
将包含该错误的每一行写入带有 的文件中sed
,遍历该文件并找到 ID,将其写入数组中。
然后我将循环for
遍历 ID 并
grep
使用初始文件查找包含 ID、任务和时间戳的行- 将任务和时间线保存到变量中
- 然后再次循环读取原始文件,以找到包含我的任务的每一行以及与我的参考接近的时间戳(以秒为单位)。
...而在这里它只是因为缓慢而死亡。
sed
在 s循环中执行sgrep
似乎并不理想,但我还没有找到从特定点读取文件的工具,例如,仅使用行 xy 或类似的东西。
由于相关行不以包含我的错误代码的行或包含我的 ID 和任务的行开头,并且其中可能有几个不相关的行,因此我觉得我必须使用某种形式的grep
.我知道第一行和最后一行的文本,但我也没有找到一种方法来利用它来发挥我的优势。
如有任何帮助,我们将不胜感激,谢谢。
编辑:是的,抱歉,有点太模糊了。现在,我通过减少外部程序调用的数量显着提高了性能。我之前所做的事情是这样的:
while read Buffer; do
TimeStamp=$(echo $Buffer | sed 's/blabla(Timestamp)blabla/\1/g')
[ TimeStamp -ge CompareStamp ] || continue
echo $Buffer >>./mylog
done
因此,我正在查看每一行并检查该行中的时间戳是否与我保存的时间戳接近。这太慢了。我用 3 个 grep 替换了该代码,这些 grep 只比较该行的时间戳部分,并查看它是否适合我的参考的第二个或之前或之后的第二个。这可行,只是超级难看。另外,我不能保证我只找到可供参考的行,因为服务器可以在 3 秒内处理该任务的多个案例。
我的日志如下所示:
timestamp first entry task blabla
timestamp blabla task blabla
timestamp blabla task blabla
timestamp blabla task reference
timestamp blabla task blabla
timestamp blabla task blabla
<xml><error>error</error></error>
timestamp blabla task blabla
timestamp last entry task blabla
我知道最后一个和第一个条目是什么,所以我可以搜索它。具有相同任务的那些块在日志文件中的该块之前和之后重复,并且其他任务的行也可以在该块内。因此,我的第一步是将包含该任务的所有行放入一个单独的文件中,以 grep 较少的数据,并且不用担心其他任务。
因此,在正常编程中,我现在将逐行读取并检查它是否是第一个条目,并始终保存最后一个第一个条目的位置,然后在找到我的引用后,我现在将返回到我保存的位置并读取每个条目行,直到找到最后一个条目。有没有办法用 shell 来达到这种效果,而不会再次将脚本减慢到人类的速度?
Edit2:好的,这是大部分内容,我刚刚删除了正则表达式和搜索字符串:
grep 'Errorcode' logfile >> ./grablog
NumOfErrors=$(grep -c 'Errorcode' grablog)
AllPrimaryReferences=($(sed -r 's/^.*(<Referencetag>)([^<]*)(<\/Referencetag>).*$/\2/g' grablog))
j=0
for ((i=0;i<NumOfErrors;i=i+2))
do
Reference=$(grep 'blablablabla = '${AllPrimaryRefernces[i]} logfile)
TimeStamp=$(echo "$Reference" | sed -r 's/^ganze Zeile/timestamp/g')
AllTasks[j]=$(echo "$Reference" | sed -r 's/ganze Zeile/Reference/g')
grep "${AllTasks[j]}" logfile >>./tempfile
CompTimeStamp=$(date -d "$TimeStamp" +%Y-%m-%d' '%X)
grep 'CompTimeStamp' tempfile >>./output
rm tempfile
let j++
done
rm grablog
´´´
答案1
您的脚本中有一些部分看起来很浪费,可以以效率的名义重新安排:
构建数组
grep 'Errorcode' logfile >> ./grablog
NumOfErrors=$(grep -c 'Errorcode' grablog)
AllPrimaryReferences=($(sed -r 's/^.*(<Referencetag>)([^<]*)(<\/Referencetag>).*$/\2/g' grablog))
您稍后使用“NumOfErrors”作为结束条件,并使用+=2
, while 循环遍历数组。您可以直接通过 访问数组的长度${#arrayname[@]}
。
这意味着您只需读取grablog
一次,或者更好的是,可以从管道中读取,从而消除临时文件:
AllPrimaryReferences=( $(grep 'Errorcode' logfile | sed -r '...' | uniq ) )
NumOfRefs=${#AllPrimaryReferences[@]}
应该uniq
删除重复的条目,而不是使用+=2
它们来跳过它们。我假设它们总是彼此配对(我无法确认,因为我看不到日志文件)。如果没有,您可以sort
在 之前添加uniq
,但如果条目很多,速度也会很慢。
不过,循环内的脚本部分是应该引起更多关注的地方。
提取变量sed
您担心sed
每个循环运行两次会很慢。您可以避免这种情况的一些方法是:
合二为一sed
您可以通过一个sed
命令将整行管道提取到一个数组中,从而将两个变量提取出来吗?
ts_task=( $(echo "$Reference" | sed 's/\(timestamp_regex\).*\(task_regex\)/\1 \2/' ) )
timestamp="${ts_task[0]}"
task="${ts_task[1]}"
使用bash
替代品
sed
也许您根本不需要,并且可以$References
从中提取您需要的子字符串参数扩展。例如。提取第一个空格之前的所有内容:
timestamp=${References%% *}
使用命名管道
如果您担心开始 sed
是慢一点,你可以在循环开始之前在后台启动它,并使用命名管道与主循环进行通信。这可能会变得很繁琐,因为您需要记住哪些进程挂在读/写状态并将它们置于后台或适当地等待它们。
读取整个输入文件两次,每次循环一次
这可能是最慢的部分,也是最有改进潜力的部分。
从参考中提取详细信息
您在评论中提到,每个引用只有一行blablabla
。这意味着第一个grep
是查看整个输入文件,并在匹配处停止;然后下一轮,又从头开始寻找下一个。
如果每个引用只有一个这样的行,则整个“构建数组”步骤可能是不必要的,您可以直接输入循环:
grep 'blablablabla = ' logfile | # match each line that defines a primary reference
sed '...' | # command to extract just the timestamp and taskname
while read ts task ; do # assign the two required variables
# use ts and task to extract everything as before
done
这意味着这两个grep
s 中的第一个现在位于循环之外,因此它只运行一次。
过滤结果
您将行的子集转储到tempfile
,然后grep
对结果运行一秒钟。和以前一样,您可以通过将这两个步骤组合到管道中来避免使用临时文件。
grep "$task" logfile | grep "$timestamp" >> output
或者如果您对整行的格式足够了解
grep "$timestamp <match other part of line> $task" logfile >> output
整个算法
即使有了所有这些改进,瓶颈也可能是您重新读取整个日志文件,并针对每个引用/任务再次检查其中的每一行。当所需的行可以以任何顺序出现在日志文件中的任何位置时,这是合适的——这是查找每一行的强力方法,因此需要很长时间。
但你已经暗示结构和上下文(“第一行和最后一行”,“第一个条目”)将允许更智能的方法。如果您对输入文件的结构/顺序了解更多,您可能可以采取进一步的快捷方式来避免重复的工作。
您问如何“保存位置”和“返回到我保存的位置”。 grep -n
将报告每场比赛的行号,以及tail
(1)命令可以从文件开头跳过多行,但仍然需要重新读取文件才能找到换行符。也许整个文件可以在一个while read
循环中处理?