【问题标题】:bash efficient file parsingbash 高效的文件解析
【发布时间】:2016-10-18 14:23:02
【问题描述】:

我有以下格式的日志文件:

20:15:35 start opsdfslkdfflkjsdlkfjlsdkfj
20:17:21 lkjlkj lklkjlkjlkjlkjlkjlkjlkjlkj
.
.
.
20:34:11 end kljsdklasjdlaksjdasdasd
20:36:20 start lksadjlaskjdalksdj
.
.
etc

在解析此文件的结果中,我想获得后续 startend 条目之间的时间差。为了保持一致性,它应该在 bash 中完成(其他日志解析是在 bash + 使用 gnuplot 绘图中完成的)。但是通过将文件重定向到while循环然后使用例如awk 将时间戳转换为秒会使整个解析非常慢(可能是由于每行都创建了新的子进程)。

while read line; do
    if [[ $string == *"start"* ]]
    then
        start=$(echo $line | awk '{print $1}' | awk -F: '{ print ($1 * 3600) + ($2 * 60) + $3 }')
        echo $start
    fi
done <log.txt

任何想法如何在 bash 中有效地完成?

【问题讨论】:

  • 您每行运行一次 awk?这太疯狂了。运行一次 awk 来处理整个文件,而不是每行输入一次。
  • 或者你可以在本地 bash 中进行数学运算——它比使用一个 awk 实例来做所有事情要慢,但比每行一个 awk 实例要快。
  • 修正,顺便说一句,涵盖识别对和打印增量 - 在本机 bash 和 awk 中。

标签: bash performance parsing awk


【解决方案1】:

它比纯 awk 实例要慢,但在本机 bash 中,仅使用 shell 内置:

while IFS=': ' read -r hr min sec content; do
  if [[ $content = *"start"* ]]; then
    start=$(( hr * 3600 + min * 60 + sec ))
    echo "$start"
  fi
done <log.txt

这也将在适当的 David Korn ksh 中运行 - 更快。 (如果使用 ksh 克隆(例如 mksh)而不是合法文章,结果,尤其是性能结果会有所不同)。


或者,对于纯 awk,您可以完全避免在 bash 中使用任何 while read 循环:

awk -F: '/start/ { print ($1 * 3600) + ($2 * 60) + $3 }' <log.txt

在 bash 中实现整个过程(识别开始/结束对并打印增量)可能如下所示:

while IFS=': ' read -r hr min sec sigil rest; do
  case $sigil in
    start) start_sec=$(( hr * 3600 + min * 60 + sec )); end_sec= ;;
    end)   end_sec=$(( hr * 3600 + min * 60 + sec ))
           if [[ $start_sec ]]; then
             echo "$start_sec->$end_sec -- $(( end_sec - start_sec )) elapsed"
             start_sec=
           fi
           ;;
  esac
done <log.txt

...或者,对于 awk 中的全部内容:

awk -F: '
  /start/ { start=( ($1 * 3600) + ($2 * 60) + $3 ) }
  /end/   { end=(   ($1 * 3600) + ($2 * 60) + $3 );
            if (start) {
              print start " -> " end " -- " (end - start) " elapsed"
              start=0
            }
          }
' <log.txt

【讨论】:

  • 我推荐/^[^[:space:]]+[[:space:]]+start[[:space:]]/ 而不是/start/,因为我们不知道还有什么在线。
  • 只是想知道:为什么要检查[[ $content = *"start"* ]] 而不是使用正则表达式并说[[ $content =~ .*start.* ]]?它们完全等价吗?
  • @fedorqui,对于正则表达式,我会做[[ $content =~ start ]] - bash 中的正则表达式不会被锚定,除非有人明确地这样做。否则——在语义上这对表达式完全等价,是的;在实施中,没有。我必须进行基准测试才能知道当前版本是否有值得一提的性能增量。
【解决方案2】:

这是一个有趣的版本,使用sed 脚本和一些没有while 循环的复合命令列表;它的执行时间应该与awk 相当,但我相信仍然更慢:需要通过一些测试来确认。

尝试使用start 打印行的时间:

sed -n '/^[0-9:]* start /{s/^\([0-9]*\):\([0-9]*\):\([0-9]*\) .*$/\1 60*\2+60*\3+p/p}' log.txt | dc

dc 是一个反抛光桌面计算器。

sed 用于选择与start 一起使用的行,并制作与dc 一起使用的字符串。

如果你的文件总是包含一对startend,按这个顺序,试试这个来计算时间差:

printf "%s %sr-p" $(sed -n '/^[0-9:]* \(start\|end\) /{s/^\([0-9]*\):\([0-9]*\):\([0-9]*\) .*$/\1 60*\2+60*\3+p/p}' log.txt | dc) | dc

printf 用于逐对打印 startend 时间,并生成另一个字符串以使第二个 dc 计算差异。

【讨论】:

  • 我不会调用 dcsed “纯 bash”——shell 也不是内置的。
  • 哈?是的,重读这个,它更像是awk 的替代解决方案。
  • @CharlesDuffy 答案已被编辑删除pure bash
  • @CharlesDuffy 我的答案也很深奥,而while 循环非常清晰。
  • 嗯,当然,但是由于您明确写这篇文章是为了好玩而不是为了清晰,所以我不会抱怨这一点。 :)
猜你喜欢
  • 1970-01-01
  • 2018-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-28
  • 2012-09-13
相关资源
最近更新 更多