我想使用sed -n "/START PATTERN/,/END PATTERN/p" file.txt
模式在文件中搜索。
file.txt
内容是
~keyword~, ~output~.
~1.~ ~output~.
~2.~ ~output~.
~keyword~, ~output~.
~1.~ ~output~.
~2.~ ~output~.
~3.~ ~output~.
~keyword blablabla~, ~not the output~.
~1.~ ~not the output~.
~2.~ ~not the output~.
~keyword blablabla2~, ~not the output~.
~1.~ ~not the output~.
~2.~ ~not the output~.
~3.~ ~not the output~.
~4.~ ~not the output~.
~blablabla~, ~not the output~.
~1.~ ~not the output~.
~2.~ ~not the output~.
~3.~ ~not the output~.
~4.~ ~not the output~.
我期望的输出是
~keyword~, ~output~.
~1.~ ~output~.
~2.~ ~output~.
~keyword~, ~output~.
~1.~ ~output~.
~2.~ ~output~.
~3.~ ~output~.
所以开始模式keyword
在中间,~
后跟任何字符.
,所以它是/~keyword~./
结束模式~
后跟任何字母字符,然后是任何 char .
。
当我运行时sed -n "/~keyword~./,/[~][[:alpha:]]./p" file.txt
输出是
~keyword~, ~output~.
~1.~ ~output~.
~keyword~, ~output~.
~1.~ ~output~.
第二行和第三行没有打印在输出中,所以我的问题是我的方法有什么问题?我通过使用提供的解决方案受到启发这里
我也尝试过sed "/~keyword~./,/[~][[:alpha:]]./!d;//d" file.txt
导致空输出(受到这个问题的启发)
这个问题与标记为重复的问题不同,因为我具体询问了有关使用 sed 进行正则表达式的问题。考虑到这一点,如果您认为这意味着它是重复的,请将其标记为重复。
答案1
让我们看看这个sed
工具是否适合这项工作:
sed '/^~[[:alpha:]].*/!{ # if line doesn't match this pattern
H # append it to hold space
$!d # and delete it if it's not the last line
b end # else branch to label end
}
//b end # if line matches, branch to label end
: end # label end
x # exchange pattern space w. hold space
/^~keyword~.*/p # if pattern space matches, print it
d' infile # delete pattern space
你gnu sed
可以把它写成一行:
sed '/^~[[:alpha:]].*/!{H;$!d;b end};//b end;: end;x;/^~keyword~.*/p;d' infile
答案2
像您正在使用的那样的模式分隔范围/P1/,/P2/
从(并包括)匹配的行开始/P1/
,到(并包括)匹配的行结束/P2/
。
您的模式没有锚定到行的开头(您可以^
在正则表达式中使用前导),因此它们可能匹配任何地方在行中。
您的“结束”模式/[~][[:alpha:]]./
与您想要保留的数据行相匹配(特别是“~欧tput" 部分),因此范围正好在第一个数据行处结束。
我打算建议让你的范围在第一行结束不匹配您的数据模式,但由于sed
不支持重叠范围,这将导致无法打印连续的“块”(如示例中的块 1 和块 2)。 (第一个块将包括第二个块的第一行。)
我可以让你对我们的主和救世主感兴趣awk
吗? ;)
awk '
BEGIN {
inrange = 0
}
/^~[[:alpha:]]/ {
inrange = 0
}
/^~keyword~/ {
inrange = 1
}
{
if (inrange) {
print
}
}'
一个解释可能是这样的:
- 上面的脚本
awk
逐行解析输入(来自文件或stdin
),就像 sed 一样。 - 在一开始(=处理第一行之前),它设置一个标志“我们不应该打印当前行”。
- 当当前行与您为“块后的第一行”指定的模式匹配时,它还会将标志设置为“不打印”。
- 当当前行与您为“块的第一行”指定的模式匹配时,它将标志设置为“执行打印”。
- 根据标志,它要么打印当前行,要么不打印。
您甚至可以通过重新排列检查顺序来排除“块开始”行(即首先打印/不打印,然后检查当前行是否是块开始)。
脚本中的换行awk
也是可选的,但大大提高了可读性。
答案3
sed
不是执行此任务的正确工具
......但这并不意味着你不能滥用它来执行你的命令:
sed -n -e "2,$ p" -e "/^~keyword~./ {s/$/§/;p}" in.txt | sed -n '/^~keyword~./,/^~[[:alpha:]]./ {/^~keyword~..*§$/ {s/§$//; b print}; /^~[[:alpha:]]./b; :print p}'
因此,在黑暗的房间里躺了一会儿,从那令人厌恶的事情中恢复过来后,它会做以下事情:
我们想要实现什么目标?
从文件中提取“块”,其中每个“块”以与正则表达式 R1 匹配的行(“开始行”)开始,并以下一次出现的正则表达式 R2 之前的行(“终止行”)结束。
那么只使用sed
的模式范围,问题出在哪里?
R2 是 R1 的子集,因此我们的“终止线”可能是新块的开始。sed
不支持重叠块。
因此,构建一个匹配 R2 但不匹配 R1 的正则表达式。
这需要零长度断言,但实际上sed
没有。 (还记得我说过sed
这不是合适的工具吗?)
解决方案:如果寻找“终止行”吞没了“起始行”,只需复制“起始行”即可。
这会起作用,但我们不能重复第一个“起始行”,否则我们只会将每个重复对视为一个块。1
sed -n -e "2,$ p" -e "/^~keyword~./ {s/$/§/;p}" in.txt
= 打印从第 2 行开始的所有行(即除第 1 行之外的所有内容)。还打印行第二次如果它们与 R1 匹配。我s/$/§/
稍后会讲到。
现在我们已经有了明确分隔的块,使用模式范围来打印块开头和终止符之间包含的所有行:sed -n '/^~keyword~./,/^~[[:alpha:]]./p'
哦等等,这包括终结者线。堆栈溢出来救援。
但我们不能跳过所有与 R2 匹配的行 - 请记住 R1 ⊂ R2,因此删除终止符行也会删除起始行。
“幸运”,sed
有分支。我们打印与 R1 匹配的所有内容,只丢弃 R2 的匹配怎么样?然后?
sed -n '/^~keyword~./,/^~[[:alpha:]]./ {/^~keyword~./b print; /^~[[:alpha:]]./b; :print p}'
太好了,现在我们正在打印重复的起始行,当它们恰好是终止行时......如果有一种方法可以区分原始起始行和它们的重复行......
这就是为什么我们这样做:在每个重复的起始行的末尾s/$/§/
添加一个(请注意,经过§'ed的重复起始行最终将成为块的起始行,而未经过§'ed的起始行将成为块的起始行)§
紧接着另一个块的终止块)。
现在我们已经获得了进行更细粒度的检查和分支所需的所有信息:
对于块范围内的所有行...
- 检查该行是否与 R1 匹配并且结尾有 §。
如果是,则删除 § 并跳转到打印该行。 - 否则(即如果我们不跳转),通过跳过所有进一步的命令(包括打印)来删除与 R2 匹配的所有行。
- 最后打印当前行。
{/^~keyword~..*§$/ {s/§$//; b print}; /^~[[:alpha:]]./b; :print p}
最终结果:
sed -n -e "2,$ p" -e "/^~keyword~./ {s/$/§/;p}" in.txt | sed -n '/^~keyword~./,/^~[[:alpha:]]./ {/^~keyword~..*§$/ {s/§$//; b print}; /^~[[:alpha:]]./b; :print p}'
但是,假设文件的第一个起始行(与 R1 匹配)位于第 1 行(请记住,这是我们在复制起始行时排除的唯一行)。如果不是,你会得到整齐的对,但没有数据:
~keyword~, ~output~.
~keyword~, ~output~.
您可能可以添加更多匹配和分支来解决这个问题,但实际上......
只需使用awk
.