7

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

例如:

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

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

4

4 回答 4

9

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

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_filegreps 并显示如下输出:number_of_line:output
  • awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}')打印stackoverflow文件中第一个和最后一个匹配的行数。

  • awk -v f=$first -v l=$last 'NR>=f && NR<=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
于 2013-10-21T14:15:45.627 回答
1

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

(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
于 2013-10-21T13:59:15.323 回答
1

对于输出是否应该包含第一行和最后一个匹配行的问题,我不能 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。

于 2013-10-21T16:39:58.443 回答
0

使用

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

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

于 2013-10-21T14:46:59.637 回答