如何获取与正则表达式匹配的第一行之后的文件部分?


169

我有一个约有1000行的文件。我想要文件的与我的grep语句匹配的行之后的部分。

那是:

$ cat file | grep 'TERMINATE'     # It is found on line 534

因此,我希望文件从535行到1000行进行进一步处理。

我怎样才能做到这一点?


34
UUOC(对猫无用):grep 'TERMINATE' file
雅各布

30
我知道,就像我那样使用它。让我们回到这个问题。
Yugal Jindle

3
这是一个非常好的编程问题,非常适合stackoverflow。
aioobe

13
@Jacob根本不是无用的猫。它的用途是打印文件到标准输出,这意味着我们可以使用grep的标准输入接口读取数据,而不是学习什么切换到适用于grep,和sed,和awkpandoc,和ffmpeg等,当我们想读从文件。这样可以节省时间,因为我们不必每次都想做同样的事情时学习新的开关:从文件中读取。
runeks

@runeks我同意您的观点-但您可以在没有cat:的情况下实现这一目标grep 'TERMINATE' < file。也许确实会使阅读起来更难-但这是shell脚本,所以总是有问题:)
LOAS

Answers:


307

下面将打印匹配的行,TERMINATE直到文件末尾:

sed -n -e '/TERMINATE/,$p'

说明: 在执行脚本后-n禁用sed打印每行的默认行为,将脚本-e指示为sed/TERMINATE/,$是地址(行)范围的选择,这意味着与TERMINATE正则表达式(如grep)匹配的第一行到文件末尾($)和p是打印当前行的打印命令。

这将从 匹配行之后的行开始打印,TERMINATE直到文件结尾:(
从匹配行之后到EOF,不包括匹配行)

sed -e '1,/TERMINATE/d'

说明: 1,/TERMINATE/是地址(行)范围的选择,表示与TERMINATE正则表达式匹配的第一行输入的第一行,并且d是删除当前行并跳至下一行的delete命令。由于sed默认行为是打印行,因此它将在TERMINATE 输入结束之后打印行。

编辑:

如果要在前面的行TERMINATE

sed -e '/TERMINATE/,$d'

并且如果您希望TERMINATE一次通过两个不同文件中的前后两行:

sed -e '1,/TERMINATE/w before
/TERMINATE/,$w after' file

之前和之后文件将包含带有terminate的行,因此要处理每个文件,您需要使用:

head -n -1 before
tail -n +2 after

编辑2:

如果您不想对sed脚本中的文件名进行硬编码,则可以:

before=before.txt
after=after.txt
sed -e "1,/TERMINATE/w $before
/TERMINATE/,\$w $after" file

但是随后您必须转义$最后一行的含义,以使shell不会尝试扩展$w变量(请注意,我们现在在脚本周围使用双引号而不是单引号)。

我忘了告诉新行在脚本中的文件名之后很重要,以便sed知道文件名结束。


编辑: 2016-0530

SébastienClément问:“如何TERMINATE用变量替换硬编码?”

您将为匹配的文本创建一个变量,然后以与前面的示例相同的方式进行操作:

matchtext=TERMINATE
before=before.txt
after=after.txt
sed -e "1,/$matchtext/w $before
/$matchtext/,\$w $after" file

在前面的示例中将变量用于匹配文本:

## Print the line containing the matching text, till the end of the file:
## (from the matching line to EOF, including the matching line)
matchtext=TERMINATE
sed -n -e "/$matchtext/,\$p"
## Print from the line that follows the line containing the 
## matching text, till the end of the file:
## (from AFTER the matching line to EOF, NOT including the matching line)
matchtext=TERMINATE
sed -e "1,/$matchtext/d"
## Print all the lines before the line containing the matching text:
## (from line-1 to BEFORE the matching line, NOT including the matching line)
matchtext=TERMINATE
sed -e "/$matchtext/,\$d"

在这些情况下,用变量替换文本的要点是:

  1. [ ]中$variablename包含的变量()不会“扩展”,但[ ]中的变量会“扩展” 。因此,如果它们包含要用变量替换的文本,则必须将所有更改为。 single quotes'double quotes"single quotesdouble quotes
  2. sed范围也包含$并紧跟像字母:$p$d$w。他们也将像变量加以扩展,所以你要逃避这些$字符用反斜杠[ \],如:\$p\$d\$w

我们如何获得TERMINATE之前的行并删除其后的所有行?
Yugal Jindle

您将如何用变量替换硬编码的TERMINAL?
塞巴斯蒂安·克莱门特

2
这里缺少的一个用例是如何在最后一个标记之后打印行(如果文件中可以有多个行,请考虑日志文件等)。
mato

该示例在第一行中出现时sed -e "1,/$matchtext/d"不起作用$matchtext。我必须将其更改为sed -e "0,/$matchtext/d"
卡洛尔加

61

作为一个简单的近似值,您可以使用

grep -A100000 TERMINATE file

它会抓紧TERMINATE并在该行之后输出最多100000行。

从手册页

-A NUM, --after-context=NUM

在匹配的行之后打印NUM行尾随上下文。 在连续的匹配组之间放置包含组分隔符(-)的行。使用-o或--only-matching选项,此选项无效,并给出警告。


这可能会起作用,但是我需要将其编码到脚本中以处理许多文件。因此,请显示一些通用解决方案。
Yugal Jindle

3
我认为这是一个切实可行的解决方案!
michelgotta

2
类似地,-B NUM,--before-context = NUM​​在匹配行之前打印NUM行前导上下文。在连续的匹配组之间放置包含组分隔符(-)的行。使用-o或--only-matching选项,此选项无效,并给出警告。
PiyusG 2014年

这个解决方案对我有用,因为我可以轻松地使用变量作为我的字符串来进行检查。
何塞·马丁内斯

3
好主意!如果您不确定上下文的大小,则可以计算以下filegrep -A$(cat file | wc -l) TERMINATE file

26

在这里使用的工具是awk:

cat file | awk 'BEGIN{ found=0} /TERMINATE/{found=1}  {if (found) print }'

这是如何运作的:

  1. 我们将变量“找到”设置为零,评估为假
  2. 如果找到与正则表达式匹配的“ TERMINATE”,则将其设置为1。
  3. 如果我们的“找到”变量的值为True,请打印:)

如果您在非常大的文件上使用其他解决方案,则可能会占用大量内存。


简单,优雅且非常通用。就我而言,它一直在打印所有内容,直到第二次出现“ ###”:cat file | awk 'BEGIN{ found=0} /###/{found=found+1} {if (found<2) print }'
Aleksander Stelmaczonek

3
这里使用的工具是catawk完全能够将一个或多个文件名作为参数。另请参见stackoverflow.com/questions/11710552/useless-use-of-cat
Tripleee

9

如果我正确理解了您的问题,那么您确实希望在之后 的行TERMINATE,而不包括-行TERMINATEawk可以用一种简单的方式做到这一点:

awk '{if(found) print} /TERMINATE/{found=1}' your_file

说明:

  1. 尽管不是最佳实践,但您可以依靠所有var默认为0或空字符串(如果未定义)的事实。因此,第一个表达式(if(found) print)将不会打印任何内容。
  2. 打印完成后,我们检查这是否是启动行(不应包括在内)。

这将打印所有行TERMINATE系法。


概括:

  • 你有一个文件的开始 -和结束 -lines,你想那些线之间的线不包括开始 -和结束 -lines。
  • 起始行结束行可以由与该行匹配的正则表达式定义。

例:

$ cat ex_file.txt 
not this line
second line
START
A good line to include
And this line
Yep
END
Nope more
...
never ever
$ awk '/END/{found=0} {if(found) print} /START/{found=1}' ex_file.txt 
A good line to include
And this line
Yep
$

说明:

  1. 如果最终发现直插没有印刷应该做的。请注意,此检查是实际打印之前进行的,以将结尾行从结果中排除。
  2. 如果found设置,则打印当前行。
  3. 如果找到起始found=1行,则进行设置,以便打印以下行。请注意,此检查是实际打印之后进行的,以将起始行从结果中排除。

笔记:

  • 该代码依赖于以下事实:所有awk-vars均默认为0,如果未定义,则为空字符串。这是有效的,但可能不是最佳做法,因此您可以BEGIN{found=0}在awk-expression的开头添加a 。
  • 如果找到多个开始-结束块,则将它们全部打印出来。

1
很棒的例子。仅仅花了2个小时来查看csplit,sed和各种复杂的awk命令。这不仅实现了我想要的功能,而且显示得足够简单,可以推断出如何对其进行修改以执行我需要的其他一些相关操作。让我记得awk很棒,而不仅仅是一团糟。谢谢。
user1169420 '19

{if(found) print}是awk中的反模式,用just替换块,found或者found;如果以后需要另一个过滤器,则更惯用。
user000001

@ user000001请解释。我不知道要替换什么以及如何替换。无论如何,我认为其编写方式非常清楚正在发生的事情。
UlfR

1
您将替换awk '{if(found) print} /TERMINATE/{found=1}' your_fileawk 'found; /TERMINATE/{found=1}' your_file,他们都应该做同样的事情。
user000001

7

使用bash参数扩展,如下所示:

content=$(cat file)
echo "${content#*TERMINATE}"

你能解释一下你在做什么吗?
Yugal Jindle

我将“文件”的内容复制到$ content变量中。然后我删除了所有字符,直到看到“ TERMINATE”。它没有使用贪婪匹配,但是您可以通过$ {content ## * TERMINATE}使用贪婪匹配。
木桥

这里是bash的手册的链接:gnu.org/software/bash/manual/...
姆乔

6
如果文件大小为100GB会怎样?
Znik 2014年

1
Downvote:这太可怕了(将文件读入变量),并且是错误的(使用不带引号的变量;您应该正确使用printf或确保确切知道要传递给什么echo。)。
三胞胎

6

grep -10000000'TERMINATE'文件

  • 比sed快得多,特别是在处理大型文件时。它最多可以处理1000万行(或您输入的任何内容),因此将其放大到足以处理您遇到的任何问题均无害。

4

有多种方法可以使用sed或进行操作awk

sed -n '/TERMINATE/,$p' file

这会TERMINATE在您的文件中查找并从该行打印到文件末尾。

awk '/TERMINATE/,0' file

这与完全相同sed

如果您知道要开始打印的行号,则可以将其与NR(记录数,最终指示行号)一起指定:

awk 'NR>=535' file

$ seq 10 > a        #generate a file with one number per line, from 1 to 10
$ sed -n '/7/,$p' a
7
8
9
10
$ awk '/7/,0' a
7
8
9
10
$ awk 'NR>=7' a
7
8
9
10

对于您也可以使用的号码more +7 file
123

这包括匹配行,这不是此问题所需要的。
mivk

@mivk很好,这也是公认的答案,也是第二个最不赞成的情况,因此问题可能出在标题误导上。
fedorqui'SO停止伤害

3

如果出于任何原因要避免使用sed,则以下命令将打印匹配行TERMINATE直到文件末尾:

tail -n "+$(grep -n 'TERMINATE' file | head -n 1 | cut -d ":" -f 1)" file

并且将从下面的匹配行中打印以下内容,TERMINATE直到文件结尾:

tail -n "+$(($(grep -n 'TERMINATE' file | head -n 1 | cut -d ":" -f 1)+1))" file

sed在一个进程中需要2个进程才能完成,如果文件在grep和tail的执行之间改变,结果可能会不一致,因此我建议使用sed。此外,如果文件完成不包含TERMINATE,则第一个命令将失败。


文件被扫描两次。如果是100GB大小怎么办?
Znik

1
因为这是一个糟糕的解决方案而被否决,但是由于90%的答案是警告,因此被否决了。
疯狂物理学家


0

这可能是一种方法。如果您知道文件的哪一行有grep字以及文件中有多少行:

grep -A466'TERMINATE'文件


1
如果知道行号,那么grep甚至不需要。您可以使用tail -n $NUM,所以这并不是真正的答案。
Samveen

-1

sed是工作的更好工具:sed -n'/ re /,$ p'文件

re是regexp。

另一个选择是grep的--after-context标志。您需要传递一个数字结尾,在文件上使用wc应该给出正确的值以结尾。将其与-n和您的match表达式结合使用。


--after-context很好,但并非在所有情况下都可以。
Yugal Jindle

你能提出其他建议吗?
Yugal Jindle

-2

这些将打印从最后找到的行“ TERMINATE”到文件结尾的所有行:

LINE_NUMBER=`grep -o -n TERMINATE $OSCAM_LOG|tail -n 1|sed "s/:/ \\'/g"|awk -F" " '{print $1}'`
tail -n +$LINE_NUMBER $YOUR_FILE_NAME

用提取行号,grep以便您可以将其馈入,这tail是一种浪费的反模式。查找匹配项并在文件末尾打印(或者相反,在第一次匹配项时打印并停止)是使用常规的基本正则表达式工具本身完成的。大量grep | tail | sed | awk的交易本身也是grep朋友的大量无用的使用
Tripleee '16

我认为他正在尝试给我们提供一些东西,该东西可以找到“ TERMINATE”的/ last实例/,并提供该实例之后的内容。其他实现为您提供了第一个实例。LINE_NUMBER应该看起来像这样:LINE_NUMBER = $(grep -o -n'TERMINATE'$ OSCAM_LOG | tail -n 1 | awk -F:'{print $ 1}')也许不是最优雅的方法,但是它似乎完成了工作。^。^
fbicknel '16

...或全部一行,但很丑:tail -n + $(grep -o -n'TERMINATE'$ YOUR_FILE_NAME | tail -n 1 | awk -F:'{print $ 1}')$ YOUR_FILE_NAME
fbicknel

....而我打算回去编辑$ OSCAM_LOG来代替$ YOUR_FILE_NAME ...,但是由于某种原因而无法执行。不知道$ OSCAM_LOG的来源;我只是漫不经心地模仿了它。oO
fbicknel '16

仅在Awk中执行此操作是Awk 101中的常见任务。如果您已经在使用功能更强大的工具来获取行号,请放开手,tail然后再使用功能更强大的工具来完成任务。无论如何,标题清楚地表明“第一场比赛”。
三胞胎
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.