如何在bash中忽略匹配前的所有行?

26

我想在Bash中忽略所有出现在匹配行之前的行(也忽略匹配行)。输入的示例可能如下:

R1-01.sql
R1-02.sql
R1-03.sql
R1-04.sql
R2-01.sql 
R2-02.sql
R2-03.sql

如果我在已排序的输入中找到匹配的 R2-01.sql,我希望得到:

R2-02.sql
R2-03.sql

我能建议您也使用“sed”标记这个问题吗?我可以自己做,但我的编辑可能不会被接受。谢谢。 - tommy.carstensen
7个回答

33

有很多种方法。例如:假设你的输入在list.txt文件中。

PATTERN="R2-01.sql"
sed "0,/$PATTERN/d" <list.txt

因为0,/pattern/只在GNU sed上有效(例如在OS X上无效),所以这里提供了一个被篡改的解决方案。 ;)

PATTERN="R2-01.sql"
(echo "dummy-line-to-the-start" ; cat - ) < list.txt | sed "1,/$PATTERN/d"

这将在开头添加一行虚拟行,因此真正的模式必须在第1行或更高位置,所以1,/pattern/将起作用-删除从第1行(虚拟行)到模式的所有内容。

或者您可以打印模式后的行并删除第一行,例如:

sed -n '/pattern/,$p' < list.txt | sed '1d'

使用awk,例如:

awk '/pattern/,0{if (!/pattern/)print}' < list.txt

或者,我最喜欢的方式是使用下一个Perl命令:

perl -ne 'print unless 1../pattern/' < list.txt

当模式在第一行时,删除第一行...

另一种解决方案是反向-删除-反向。

tail -r < list.txt | sed '/pattern/,$d' | tail -r

如果你有tac命令,使用它替代tail -r。有趣的是/pattern/,$d'在最后一行起作用,但是1,/pattern/d`在第一行却不起作用。


我正在使用这个,但是当匹配发生在第一行时它不起作用。请参阅 https://dev59.com/2mQm5IYBdhLWcg3wwxLF - Fabio
@Fabio 是的!正确的解决方案是0,/pattern/。谢谢,并添加了一个适用于非GNU sed的解决方案。 - clt60
1
反转-删除-反转技术在 http://stackoverflow.com/questions/38662085/grep-log4j-for-lines-after-certain-timestamp-in-java 中挽救了局面。 - jonayreyes
大多数这些只适用于硬编码的“pattern”字符串,而不是更有用的“$PATTERN” shell变量。 - user5359531

17

如何在bash中忽略匹配前的所有行?

问题标题和您的示例不完全对应。

使用sed,打印从"R2-01.sql"开始的所有行:

sed -n '/R2-01.sql/,$p' input_file.txt

问题所在:

  • -n 抑制将模式空间打印到标准输出(stdout)
  • / 开始和结束匹配的模式(正则表达式)
  • , 分隔起始范围和结束范围
  • $ 定位到输入中的最后一行
  • p 在该范围内回显模式空间到stdout
  • input_file.txt 是输入文件

sed中打印所有在"R2-01.sql"之后的行:

sed '1,/R2-01.sql/d' input_file.txt
  • 1表示输入的第一行
  • ,分离起始范围和结束范围
  • /开始和结束匹配模式(正则表达式)
  • $表示输入中的最后一行
  • d删除该范围内的模式空间
  • input_file.txt是输入文件
  • 未被删除的所有内容都会被输出到标准输出。

那会打印带有模式的行吗?似乎OP不想要那个。 - glenn jackman
@glenn:这取决于你读问题的哪个部分。我已经更新了我的答案。 - johnsyweb
这仅适用于硬编码的 R2-01.sql,而不是 $PATTERN - user5359531
@user5359531:正确。虽然问题中没有提到$PATTERN,但是这可以相对容易地进行调整。 - johnsyweb
谢谢!这对我来说是迄今为止最好的答案。是否可能在模式之前跳过所有内容以及模式后的3行,同时排除带有模式的行? - tommy.carstensen

9
这种方法有些取巧,但易于记忆,可以快速获得所需输出:
$ grep -A99999 $match $file

显然,您需要选择一个足够大的值来匹配所有内容;如果使用过小的值,则输出将被默默截断。
为确保您获得所有输出,可以执行以下操作:
$ grep -A$(wc -l $file) $match $file

当然,那时你可能最好使用sed解决方案,因为它们不需要读取文件两次。

如果你不想要匹配的行本身,你可以将此命令简单地导入tail -n+1跳过输出的第一行


如果这个文件包含超过99999行怎么办? 我处理的文件超过了100000行,幸运的是我没有听从你的建议。 - SansWord
第二个示例适用于任何大小的文件。正如我所说,第一个示例很有用,因为它易于记忆。这是sed解决方案的替代方案,如果您不想这样做,可以不使用它。 - dimo414
3
我见过的唯一真正可移植和灵活的解决方案是使用文件的后一个 grep 命令,配合 wc -l - user5359531

3
awk -v pattern=R2-01.sql '
  print_it {print} 
  $0 ~ pattern {print_it = 1}
'

1
Perl是另一个选择:

perl -ne 'if ($f){print} elsif (/R2-01\.sql/){$f++}' sql

要将正则表达式作为参数传递,请使用-s启用简单的参数解析器。

perl -sne 'if ($f){print} elsif (/$r/){$f++}' -- -r=R2-01\\.sql file

1

你可以这样做,但我认为jomo666的答案更好。

sed -nr '/R2-01.sql/,${/R2-01/d;p}' <<END
    R1-01.sql
    R1-02.sql
    R1-03.sql
    R1-04.sql
    R2-01.sql
    R2-02.sql
    R2-03.sql
    END

0
这可以通过使用grep实现,通过打印足够多的上下文来跟随$match。该示例将输出第一个匹配行,后跟999,999行的“上下文”。
grep -A999999 $match $file

为了增加安全性(例如,如果$match以连字符开头),您应该使用-e来强制使用$match作为表达式。
grep -A999999 -e '$match' $file

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接