【问题标题】:unix command to get lines from in between first and last occurence of a word and write to a fileunix命令从单词的第一次和最后一次出现之间获取行并写入文件
【发布时间】:2013-10-22 01:49:04
【问题描述】:

我想要一个 unix 命令来查找单词的第一次和最后一次出现之间的行

例如:

假设我们有 1000 行。第十行包含单词“stackoverflow”,第三十五行还包含单词“stackoverflow”。

我想打印 10 到 35 之间的行并将其写入新文件。

【问题讨论】:

  • 与其想象,为什么不创建一个示例输入文件和预期的输出供我们处理?

标签: bash shell unix grep


【解决方案1】:

您可以分两步完成。基本思路是:

1) 获取第一个和最后一个匹配的行号。

2) 打印这些范围之间的行范围。

$ read first last <<< $(grep -n stackoverflow your_file | awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}')
$ awk -v f=$first -v l=$last 'NR>=f && NR<=l' your_file

说明

  • read first last 读取两个值并将它们存储在 $first$last 中。
  • grep -n stackoverflow your_file greps 并显示如下输出:number_of_line:output
  • awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}') 打印文件中stackoverflow 的第一个和最后一个匹配的行号。

  • awk -v f=$first -v l=$last 'NR&gt;=f &amp;&amp; NR&lt;=l' your_file 打印从$first 行号到$last 行号的所有行。

测试

$ cat a
here we
have some text
stackoverflow

and other things
bla
bla
bla bla
stackoverflow
and whatever else
stackoverflow
to make more fun
blablabla

$ read first last <<< $(grep -n stackoverflow a | awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}')
$ awk -v f=$first -v l=$last 'NR>=f && NR<=l' a
stackoverflow

and other things
bla
bla
bla bla
stackoverflow
and whatever else
stackoverflow

按步骤:

$ grep -n stackoverflow a
3:stackoverflow
9:stackoverflow
11:stackoverflow

$ grep -n stackoverflow a | awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}'
3 11

$ read first last <<< $(grep -n stackoverflow a | awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}')

$ echo "first=$first, last=$last"
first=3, last=11

【讨论】:

    【解决方案2】:

    如果您知道可以有多少行的上限(比如一百万行),那么您可以使用这个简单的滥用脚本:

    (grep -A 100000 stackoverflow | grep -B 1000000 stackoverflow) < file
    

    您也可以附加| tail -n +2 | head -n -1 来去除边框线:

    (grep -A 100000 stackoverflow | grep -B 1000000 stackoverflow
      | tail -n +2 | head -n -1) < file
    

    【讨论】:

      【解决方案3】:

      对于输出是否应该包含第一行和最后一个匹配行的问题,我不能 100% 确定,所以我假设它是。但是如果我们想要独占,这可以很容易地改变。

      这个纯 bash 解决方案一步完成 - 即文件(或管道)只读取一次:

      #!/bin/bash
      
      function midgrep {
          while read ln; do
              [ "$saveline" ] && linea[$((i++))]=$ln
              if [[ $ln =~ $1 ]]; then
                  if [ "$saveline" ]; then
                      for ((j=0; j<i; j++)); do echo ${linea[$j]}; done
                      i=0
                  else
                      saveline=1
                      linea[$((i++))]=$ln
                  fi
              fi
          done
      }
      
      midgrep "$1"
      

      将其保存为脚本(例如 midgrep.sh)并将您喜欢的任何输出通过管道传递给它,如下所示:

      $ cat input.txt | ./midgrep.sh stackoverflow
      

      它的工作原理如下:

      • 在数组的第一个元素中找到第一个匹配行和缓冲区
      • 继续读取行,直到下一个匹配,在我们进行时缓冲到数组
      • 在每个后续匹配中,刷新缓冲区数组以输出
      • 继续阅读文件到最后。如果没有更多匹配项,则简单地丢弃最后一个缓冲区。

      这种方法的优点是我们只读取一次输入。缺点是我们在每次匹配之间缓冲所有内容 - 如果每次匹配之间有很多行,那么这些都缓冲到内存中,直到我们遇到下一个匹配。

      这也使用了 bash =~ 正则表达式运算符来保持这个纯 bash。但是,如果您对此更满意,则可以将其替换为 grep。

      【讨论】:

        【解决方案4】:

        使用

        perl -00 -lne '
            chomp(my @arr = split /stackoverflow/);
            print join "\nstackoverflow", @arr[1 .. $#arr -1 ]
        ' file.txt | tee newfile.txt
        

        这背后的想法是使用“stackoverflow”字符串将整个输入文件的数组输入到块中以进行拆分。接下来,我们使用 join "stackoverflow" 将第 2 个匹配项打印到最后一个 -1。

        【讨论】:

          猜你喜欢
          • 2017-07-11
          • 1970-01-01
          • 2013-12-28
          • 2017-01-21
          • 1970-01-01
          • 1970-01-01
          • 2015-06-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多