sed 范围并不总是能够只匹配一行

sed 范围并不总是能够只匹配一行

考虑以下范围:1,/pattern/.如果模式在第一行匹配,则范围匹配整个文件:

$ cat 1.sh
#!/usr/bin/env bash
set -eu
seq 1 4 | sed -rn '1,/'"$1"'/p'
$ ./1.sh 1
1
2
3
4
$ ./1.sh 2
1
2

你会怎么做呢?

UPD这是我所做的(以防万一):

re='/1/'
seq 1 4 | sed -rn "1{$re{p;q}}; 1,${re}p"

或者这样:

seq 1 4 | sed -rn "1{/1/{p;q}}; 1,//p"

答案1

是的,这是一件烦人的事情sed(请参阅sed常问问题关于这一点)。由于您使用的是 GNU sed-r特定于 GNU),因此您可以执行以下操作:

 sed -En "0,/$1/p"

(我-E更喜欢它,-r因为它也受到sedFreeBSD 等其他一些工具的支持,并且与其他一些工具一致grep(并且将在POSIX/Single UNIX 规范标准的下一期))。

更好的替代方案(并且便携)是:

sed "/$1/q"

sed在第一场比赛后告诉退出(并停止阅读)。

请注意,awk没有问题,因此您可以编写:

PATTERN=$1 awk 'NR==1, $0 ~ ENVIRON["PATTERN"]'

(虽然像 for sed,你宁愿写):

PATTERN=$1 awk '1; $0 ~ ENVIRON["PATTERN"] {exit}'

答案2

这是 的正常行为sed。来自 POSIXsed文档:

sed 中的地址

地址可以是对文件中的输入行进行累积计数的十进制数、对输入的最后一行进行寻址的“$”字符,或者是上下文地址(由 BRE 组成,如 sed 中的正则表达式中所述),前面和后面由分隔符(通常是斜杠)。

没有地址的编辑命令应选择每个模式空间。

具有一个地址的编辑命令应选择与该地址匹配的每个模式空间。

具有两个地址的编辑命令应选择从与第一个地址匹配的第一个模式空间到与第二个地址匹配的下一个模式空间的包含范围。。 (如果第二个地址的数字小于或等于第一次选择的行号,则只能选择一行。)从所选范围之后的第一行开始,sed 将再次查找第一个地址。此后,应重复该过程。省略以下形式的一个或两个地址部分会产生未定义的结果:

[地址[,地址]]

您可以看到,sed将打印从第一个地址到下一个匹配地址的包含范围。

在您的情况下1,/1/psed打印第一行,因为它与地址匹配1。然后从第二行开始,sed 将搜索与pattern 匹配的第二个地址/1/。如果发现则停止打印。因为从第二行开始,您没有任何匹配的模式/1/,因此sed打印其余部分。

在使用 的情况下1./2/p,sed 如上所述打印第一行,然后第二行匹配模式/2/sed打印它并重复其余操作。但您无法匹配1其余部分的地址,因此sed不会打印任何内容。

一个例子:

$ echo 1 2 3 1 4 1 | tr ' ' $'\n' | sed -rn '1,/1/p'
1
2
3
1

因为你使用GNU sed,所以你可以使用 form 0,addr2

0,addr2
              Start  out  in  "matched  first  address"  state, until addr2 is
              found.  This is similar to 1,addr2, except that if addr2 matches
              the very first line of input the 0,addr2 form will be at the end
              of its range, whereas the 1,addr2 form  will  still  be  at  the
              beginning of its range.  This works only when addr2 is a regular
              expression.

所以,你的命令变成:

seq 1 4 | tr ' ' $'\n' | sed -rn '0,/'"$1"'/p'

然后:

$ ./1.sh 1
1

答案3

您可以做几件事。例如,您的评论表明您的意思是:

...删除从文件开头到某个特定行的所有内容,而该行恰好是第一行...

你可以这样做:

sed -n "/$1"'/,$p'

你只需颠倒表格即可。上面的命令只会从某个特定行打印到文件末尾。

如果您不想打印该特定行...

sed -n "/$1"'/,$p' | sed 1d

...应该做的伎俩...

否则,您可以直接解决该问题,然后将周期掌握在自己手中。

seq 20 | sed -ne"/$1"'/!d;:B' -e'n;p;bB'
seq 20 | sed -n "/$1"'/!d;h;n;G;P;D'

这两个命令d都会删除每个传入的行,直到遇到$1模式。

然后,第一个命令用n外部输入行覆盖模式空间并设置:b标签。然后p打印该行并再次用n外部行覆盖模式空间,然后b再返回到:b标签。它以这种方式循环直到文件结束。此命令可能比第二个命令更快 - 它执行的操作更少。

第二个h用匹配覆盖旧空间$1。然后,它还会使用输入上的 ext 行覆盖模式空间n。接下来,它G保留空间并将其附加到刚刚拉入的输入行 - 从而反转两行的顺序并用换行符在它们之间进行分隔。就像是:

  • 第 1 行 > 保留空间

  • 2 号线 > 1 号线

  • 保留空间 >> 第 2 行

  • = 第 2 行 \n 第 1 行

此时,sed P仅打印\n模式空间中出现的第一个 ewline 字符,并且D在用剩余部分重新启动循环之前相同的 eletes - 结束总是$1是第一个匹配的行每行。所以第一行匹配$1总是在模式空间中但是绝不打印。

因此,如果$1打印5以下内容:

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

答案4

当您在包含 quit 命令的范围后使用函数列表时,您可以保留sed范围并且不使用-E或选项(使用 FreeBSD 和 GNU 进行测试)。-rsedqsed

printf '%s\n' {1..10} | sed -n '1,/1/{p;q;}'

# your solution adapted to work with FreeBSD sed as well
re='/1/'
printf '%s\n' {1..4} | sed -En "1{$re{p;q;};}; 1,${re}p"
printf '%s\n' {1..4} | sed -En "1{/1/{p;q;};}; 1,//p"

相关内容