打印两个模式之间的开始和结束(不包括范围结束)

打印两个模式之间的开始和结束(不包括范围结束)

我想使用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.

相关内容