【问题标题】:grep -B emulation with ring buffer / awkgrep -B 模拟环形缓冲区/awk
【发布时间】:2012-01-13 06:33:19
【问题描述】:

我需要在我的搜索字符串上方提取一行(例如,上面的 19 行)。通常情况下,我会选择

grep -B 19 $search_string $file | ...further processing

不过,该脚本也可以在 Solaris 上使用,其中 grep 不提供 -B 选项。通常,如果我知道前面的几行,我可以使用awk '/begin/,/end/' 打印一堆行。在这种特殊情况下,这是不可能的。我尝试了以下方法:

1) 环形缓冲溶液。

#!/bin/bash
g_a_buffer=( 0 )
g_i_buffer_index=1
while read line
        do
        g_a_buffer[$((g_i_buffer_index % 20))]=$line
        echo $line|grep $search_string > /dev/null
        [ $? -eq 0 ] && echo ${g_a_buffer[$(( (g_i_buffer_index + 2) % 20))]}
        let "g_i_buffer_index += 1"
        done < $file_name

这是非常慢。大约 40k 行需要 1m37s(grep 需要 0.005s)

2) awk 解决方案。我必须直截了当地说,我是 awk 的极端初学者,很少超越 awk '{print $1}'。 以下行不起作用,但可以让您了解我想要实现的目标:

awk '/mySearchString/ {print NR-19}' filename.txt 

0.118s执行,速度不错!但我得到的只是一个行号 - 19。我需要的是位于 (line - 19) 的行的打印输出。经过一番谷歌搜索后,我仍然找不到答案。我承认这一定是一个非常基本的问题,但我似乎在这里碰壁了。

到目前为止,我发现的只是如何使用 awk 打印前一行(这是一种 1 行缓冲区),或者使用环形缓冲区但在 awk 中的大规模实现。有没有更优雅的方法来做到这一点?

感谢您的帮助!

【问题讨论】:

  • grep 通常是一个与 bash 不同的程序,许多 Solaris 上的人只是在备用路径 (/opt/gnu) 中安装 GNU 工具(如 grep)以补充他们古老的 Sun 变体。在您的情况下可能会更有效。
  • Thiton,不幸的是这是不可能的,我必须处理可用的东西。
  • Derobert:这就是语义发挥作用的地方——perl 肯定可以在相关的 Solaris 服务器上使用,但我对 perl 的经验完全为零。所以是的,我可以,但我不能 :) 我将使用您的解决方案与下面的grep -n 一起运行,谢谢!

标签: arrays bash awk grep


【解决方案1】:

这是一个需要两次遍历文件的解决方案,因此不是最佳的,但在实践中可能会很好地执行。 (在 GNU awk 上测试过,但没有明显的理由说明它不能在 Solaris 上运行)。

awk "$(awk '/mySearchString/ { print "NR==" NR-19 }' myInputFile.txt)" myInputFile.txt

由于这需要两次传递,如果您从其他地方通过管道传输输入,则需要将其存储在某个临时文件中。

或者,如果您知道您的搜索字符串将在文件中最多出现一次(或者至少您只关心第一次出现),您可以将 awk 与 head 和 tail 组合以提取该行:

awk 'NR==1,/mySearchString/' | tail -n 19 | head -n 1

我没有合适的文本文件方便地对此进行基准测试,但我希望它比您的环形缓冲区解决方案好很多。

【讨论】:

  • Theo,出现的次数比 1 多(比方说 3 到 6)
  • Theo, awk "$(awk '/mySearchString/ { print "NR==" NR-19 }' myInputFile.txt)" myInputFile.txt 实际上工作得很好!!我会选择这个,我认为它比grep -n / head / tailsolution 更优雅。谢谢!
【解决方案2】:

您可能可以使用grep -n(应该在那里,因为-n 由POSIX 指定)来获取每个匹配项的行号。

file="foo"
for line in $(grep -n "pattern" "$file" | cut -d: -f1); do
  end=`expr $line + 1`
  head -n $end "$file" | tail -n 3
done

那是-B 1,但听起来你只想要n-19,所以你可以这样做:

  target=`expr $line - 19`
  head -n $target "$file" | tail -n 1 

不会像 grep 那样快,而且我没有处理 -B 1 情况下可能出现的重叠(将输出两次行),但应该可以。如果有的话,可以使用grep -b(用于字节偏移)进行优化。

【讨论】:

  • Derobert,是的 grep -n 在 Solaris 上工作,所以我现在可以使用 head -N|tail -1 的技巧了。谢谢!!
【解决方案3】:
$ cat mySearcher.sh
#!/bin/ksh

awk '{ array[i++]=$0 }
     END {
       maxI=++i
      for (j=0;j<maxI; j++) {
        if (array[j] ~ /'"${1}"'/) {  #searchTarget
           print array[j-19]
        }
      }
     }
   ' "$2"

使其可执行

$ chmod 755 mySearch.sh

称为

$ mySearcher.sh "search target" file

应该是解决问题的良好开端

您正在将所有数据读入 awk 数组(怪物文件可能是个问题), 然后在 END 块中,循环遍历数组,将每条记录与您的搜索目标匹配'

这不能很好地处理您的 searchTarget 在第 19 行之前的情况。您还可以修改此脚本以使用与 $1 相同的技术作为搜索目标,以使“回溯”数字成为参数。

我希望这会有所帮助。

【讨论】:

  • 谢尔特,非常感谢。我怀疑如果在 awk 中实现一个小的环形缓冲区(20 行)会更有效(正如我所了解的,在处理数组时,bash 绝对不是一种方法),尤其是。看看我如何处理每个 2-3MB 的多个文件。我不敢将整个文件加载到内存中,因为这是一个有大量用户的服务器。无论如何,谢谢!
【解决方案4】:

这可能对你有用:

sed -n ':a;s/\n/&/19;tc;:b;$q;N;ba;:c;/\nPATTERN$/{h;x;s/\n.*//p;x};s/^[^\n]*\n//;ta'

另一个便宜又讨厌的(不处理重叠)是:

tac | sed -n '/PATTERN/,+19{h;d};x;/^$/!{p;s/.*//};x' | tac

两者都可能需要 GNU sed

【讨论】:

  • 波东,谢谢回复!我选择了 awk 方式——至少我能理解那里发生了什么:) 作为一个小小的抱怨,我在 mac 上没有 tac ..
【解决方案5】:

你几乎明白了! “正确”的 AWK 答案是:

awk '$NF ~ "regex" {print $(NF-1)}' input_file

【讨论】:

    猜你喜欢
    • 2012-04-04
    • 1970-01-01
    • 2018-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-02
    • 1970-01-01
    相关资源
    最近更新 更多