如何获取与正则表达式匹配的第一行之后的文件部分
Posted
技术标签:
【中文标题】如何获取与正则表达式匹配的第一行之后的文件部分【英文标题】:How to get the part of a file after the first line that matches a regular expression 【发布时间】:2011-10-29 12:46:56 【问题描述】:我有一个大约 1000 行的文件。我想要在与我的 grep 语句匹配的行之后的文件部分。
即:
cat file | grep 'TERMINATE' # It is found on line 534
所以,我想要从第 535 行到第 1000 行的文件进行进一步处理。
我该怎么做?
【问题讨论】:
UUOC(无用的猫):grep 'TERMINATE' file
我知道,就像我那样使用它。让我们回到这个问题。
这是一个非常好的编程问题,非常适合***。
@Jacob 根本不是无用的 cat 。它的用途是将文件打印到标准输出,这意味着我们可以使用grep
s 标准输入接口来读取数据,而不必学习对grep
、sed
、@987654326 应用什么开关@, and pandoc
, and ffmpeg
etc. 当我们想从文件中读取时。它节省了时间,因为我们不必每次想要做同样的事情时都学习新的开关:从文件中读取。
@runeks 我同意你的观点——但你可以在没有猫的情况下实现这一点:grep 'TERMINATE' < file
。也许它确实使阅读有点困难 - 但这是 shell 脚本,所以这总是一个问题 :)
【参考方案1】:
下面将打印匹配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
是删除命令删除当前行并跳到下一行。由于sed
默认行为是打印行,它会将TERMINATE
之后的行打印到输入的末尾。
如果你想要TERMINATE
之前的行:
sed -e '/TERMINATE/,$d'
如果你想在两个不同的文件中一次传递TERMINATE
之前和之后的两行:
sed -e '1,/TERMINATE/w before
/TERMINATE/,$w after' file
before 和 after 文件将包含带有 terminate 的行,因此要处理每个您需要使用的行:
head -n -1 before
tail -n +2 after
如果您不想在 sed 脚本中硬编码文件名,您可以:
before=before.txt
after=after.txt
sed -e "1,/TERMINATE/w $before
/TERMINATE/,\$w $after" file
但是你必须转义最后一行的$
,这样shell就不会尝试扩展$w
变量(请注意,我们现在在脚本周围使用双引号而不是单引号)。
我忘了告诉脚本中文件名后面的新行很重要,以便 sed 知道文件名结束。
如何将硬编码的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"
在这些情况下用变量替换文本的要点是:
single quotes
['
] 中的变量 ($variablename
) 不会“扩展”,但 double quotes
["
] 中的变量会。因此,如果 single quotes
包含要替换为变量的文本,则必须将它们全部更改为 double quotes
。
sed
范围还包含一个$
,后面紧跟一个字母,例如:$p
、$d
、$w
。它们也看起来像要扩展的变量,因此您必须使用反斜杠 [\
] 转义那些 $
字符,例如:\$p
、\$d
、\$w
。
【讨论】:
我们如何获取 TERMINATE 之前的行并删除后面的所有行? 如何用变量替换硬编码的 TERMINAL? 这里缺少的一个用例是如何在最后一个标记之后打印行(如果文件中可以有多个..想想日志文件等)。 当$matchtext
出现在第一行时,示例sed -e "1,/$matchtext/d"
不起作用。我不得不把它改成sed -e "0,/$matchtext/d"
。
一站式解决我的问题。更喜欢 double upvote 这个答案,但我不能。【参考方案2】:
作为一个简单的近似值,您可以使用
grep -A100000 TERMINATE file
其中 greps 为 TERMINATE
并在该行之后输出最多 100,000 行。
来自the man page:
-A NUM, --after-context=NUM
在匹配行之后打印 NUM 行尾随上下文。 在中间放置一个包含组分隔符 (--) 的行 连续的匹配组。使用 -o 或 --only-matching 选项,这不起作用并给出警告。
【讨论】:
这可能适用于此,但我需要将其编码到我的脚本中以处理许多文件。所以,展示一些通用的解决方案。 我认为这是一种实用的解决方案! 类似地 -B NUM, --before-context=NUM 在匹配行之前打印前导上下文的 NUM 行。在连续的匹配组之间放置一个包含组分隔符 (--) 的行。使用 -o 或 --only-matching 选项,这不起作用并给出警告。 这个解决方案对我有用,因为我可以很容易地使用变量作为我的字符串来检查。 好主意!如果您不确定上下文的大小,您可以计算file
的行数:grep -A$(cat file | wc -l) TERMINATE file
【参考方案3】:
这里使用的工具是AWK:
cat file | awk 'BEGIN found=0 /TERMINATE/found=1 if (found) print '
这是如何工作的:
-
我们将变量 'found' 设置为零,评估结果为假
如果在正则表达式中找到“TERMINATE”的匹配项,我们将其设置为 1。
如果我们的 'found' 变量计算结果为 True,则打印 :)
如果您在非常大的文件上使用其他解决方案,它们可能会消耗大量内存。
【讨论】:
简单、优雅且非常通用。就我而言,它正在打印所有内容,直到第二次出现“###”:cat file | awk 'BEGIN found=0 /###/found=found+1 if (found<2) print '
此处不使用的工具是cat
。 awk
完全能够将一个或多个文件名作为参数。另见***.com/questions/11710552/useless-use-of-cat【参考方案4】:
如果我正确理解您的问题,您确实需要 TERMINATE
之后的行,不包括 TERMINATE
行。 AWK 可以通过简单的方式做到这一点:
awk 'if(found) print /TERMINATE/found=1' your_file
解释:
-
虽然不是最佳实践,但您可以依靠所有变量默认为 0 或空字符串(如果未定义)这一事实。所以第一个表达式 (
if(found) print
) 不会打印任何内容。
打印完成后,我们检查这是否是起始行(不应包含在内)。
这将打印 TERMINATE
-line 之后的所有行。
概括:
您有一个包含 start- 和 end- 行的文件,并且您希望这些行之间的行不包括 start- 和 end- 行。 start- 和 end- 行可以由匹配该行的正则表达式定义。例子:
$ 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
$
解释:
-
如果发现 end 行,则不应进行打印。请注意,此检查在实际打印之前完成,以从结果中排除 end-行。
如果设置了
found
,则打印当前行。
如果找到 start 行,则设置 found=1
以便打印以下行。请注意,此检查是在在实际打印之后完成的,以从结果中排除 start-行。
注意事项:
代码依赖于这样一个事实,即所有 AWK 变量默认为 0 或空字符串(如果未定义)。这是有效的,但它可能不是最佳做法,因此您可以在 AWK 表达式的开头添加BEGINfound=0
。
如果找到多个start-end-blocks,它们都会被打印出来。
【讨论】:
真棒真棒的例子。刚刚花了 2 个小时研究 csplit、sed 和各种复杂的 awk 命令。这不仅做了我想要的,而且显示得足够简单,可以推断出如何修改它来做一些我需要的其他相关事情。让我记得 awk 很棒,而不仅仅是难以理解的废话。谢谢。if(found) print
是 awk 中的一种反模式,如果您之后需要另一个过滤器,将块替换为 found
或 found;
更为惯用。
@user000001 请解释一下。我不明白要更换什么以及如何更换。无论如何,我认为它的写作方式非常清楚发生了什么。
您可以将awk 'if(found) print /TERMINATE/found=1' your_file
替换为awk 'found; /TERMINATE/found=1' your_file
,它们都应该做同样的事情。【参考方案5】:
grep -A 10000000 'TERMINATE' file
比 sed 快得多,尤其是处理非常大的文件。它最多可以工作 10M 行(或您输入的任何内容),因此将其制作得足够大以处理您遇到的任何内容都没有任何害处。
【讨论】:
你说的“处理你碰到的任何东西”是什么意思(似乎难以理解)?请通过editing (changing) your answer 回复,而不是在 cmets 中(without "Edit:"、"Update:" 或类似的 - 答案应该看起来像是今天写的)。【参考方案6】:使用 Bash 参数扩展,如下所示:
content=$(cat file)
echo "$content#*TERMINATE"
【讨论】:
你能解释一下你在做什么吗? 我将“文件”的内容复制到 $content 变量中。然后我删除了所有字符,直到看到“TERMINATE”。它没有使用贪婪匹配,但你可以通过 $content##*TERMINATE 使用贪婪匹配。 这里是 bash 手册的链接:gnu.org/software/bash/manual/… 如果文件大小为 100GB 会怎样? 投反对票:这是可怕的(将文件读入变量)和错误的(使用变量而不引用它;你应该正确使用printf
或确保你确切知道你传递给什么echo
.).【参考方案7】:
使用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
的号码
这包括匹配行,这不是这个问题想要的。
@mivk 好吧,这也是被接受的答案和第二个最赞成的情况,所以问题可能与误导性标题有关。【参考方案8】:
如果出于任何原因,您想避免使用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 在一个进程中可以做的事情需要两个进程,如果文件在执行 grep 和 tail 之间发生变化,结果可能会不连贯,所以我推荐使用 sed。此外,如果文件不包含TERMINATE
,则第一个命令会失败。
【讨论】:
文件被扫描两次。如果它是 100GB 大小怎么办? 投了反对票,因为这是一个糟糕的解决方案,但随后投了赞成票,因为 90% 的答案都是警告。【参考方案9】:优秀sed
answer by jfg956的替代品,不包括匹配行:
awk '/TERMINATE/ y=1;next y'
(Hai Vu's answer to 'grep +A': print everything after a match)
awk '/TERMINATE/ ? c++ : c'
(Steven Penny's answer to 'grep +A': print everything after a match)
perl -ne 'print unless 1 .. /TERMINATE/'
(tchrist's answer to 'grep +A': print everything after a match)
【讨论】:
【参考方案10】:这可能是一种方法。如果您知道 grep 单词在文件的哪一行以及文件中有多少行:
grep -A466 'TERMINATE' file
【讨论】:
如果行号已知,则grep
甚至不需要;你可以使用tail -n $NUM
,所以这不是一个真正的答案。【参考方案11】:
sed 是一个更好的工具:
sed -n '/re/,$p' file
re
是一个正则表达式。
另一个选项是grep 的--after-context
标志。您需要传入一个数字以结束,在文件上使用wc 应该给出正确的值以停止。将此与 -n
和您的匹配表达式结合使用。
【讨论】:
--after-context 很好,但不是在所有情况下。 你能推荐点别的吗.. ??【参考方案12】:这将打印从最后找到的行“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
本身也是一个巨大的useless use of grep
and friends。
我认为 s*he 试图给我们一些东西,可以找到“TERMINATE”的 /last instance/ 并给出该实例的行。其他实现为您提供第一个实例。 LINE_NUMBER 应该看起来像这样,而不是: LINE_NUMBER=$(grep -o -n 'TERMINATE' $OSCAM_LOG | tail -n 1| awk -F: 'print $1') 也许不是最优雅的方式,但它似乎完成了工作。 ^.^
... 或全部在一行,但丑陋:tail -n +$(grep -o -n 'TERMINATE' $YOUR_FILE_NAME | tail -n 1| awk -F: 'print $1 ') $YOUR_FILE_NAME
.... 我打算回去编辑 $OSCAM_LOG 代替 $YOUR_FILE_NAME... 但由于某种原因不能。不知道 $OSCAM_LOG 来自哪里;我只是漫不经心地鹦鹉学舌。 o.O
仅在 Awk 中执行此操作是 Awk 101 中的常见任务。如果您已经使用功能更强大的工具来获取行号,请放开 tail
并在 more 中执行任务完全有能力的工具。不管怎样,标题清楚地写着“第一场比赛”。以上是关于如何获取与正则表达式匹配的第一行之后的文件部分的主要内容,如果未能解决你的问题,请参考以下文章