-z

-z

我了解到,使用 sed,可以通过使用空数据模式 ( -z):来匹配模式的第 n 次出现sed -z 's/foo/bar/2'

使用时如何匹配行首-z

如果我执行:

echo $'foo\nfoo\nfoo' | sed -z 's/^foo/baz/2'

没有替换,因为^表示整个字符串的开头:

$ echo $'foo\nfoo\nfoo' | sed -z 's/^foo/baz/g'
baz
foo
foo

在 Perl 中,有m正则表达式修饰符(当发出声音时),但在 sed 中它没有帮助。

答案1

echo $'foo\nfoo\nfoo' | sed -Ez 's/(^|\n)foo/\1baz/2'

^需要正确计算foo恰好位于第一行开头的内容。

请注意,代码计算的是(^|\n)foo,而不是 的出现次数foo。如果你想计算foos,但替换仅有的当所需的出现恰好位于行的开头时,则此代码不是解决方案。例如在:

echo $'foo foo\nfoo foo\nfoo foo' | sed -Ez 's/(^|\n)foo/\1baz/3'

foo替换的已经不是第三次了foo

使用 GNU sed4.8 进行测试。

答案2

并不像所选答案看起来那么简单。首先,2(或 s///2 中的任何其他数字)的实际含义是什么?它只是意味着应用它的行上的第二个正则表达式匹配将被更改。

$ printf '%s\n' 'foo foo foo' 'foo foo foo' 'foo foo foo' | sed 's/foo/bar/2'
foo bar foo
foo bar foo
foo bar foo

sed 命令更改了foowith barbut for 的第二个 (2) 实例每个它应用到的行(所有行)。可以将其更改为仅在一行或一系列行上工作:

printf '%printf '%s\n' 'foo foo foo'{,,,,} | sed '3,4s/foo/bar/2'
foo foo foo
foo foo foo
foo bar foo
foo bar foo
foo foo foo

请注意,只有行34被更改,而不是全部,并且在所有这些行中,foo被更改的实例是第二个 (2)。

这就是工作原理s/foo/bar/2

-z

现在,如果-z使用 if ,则行以\0(not \n) 结尾。但替换的工作方式完全相同(用\0代替\n):

$ printf '%s\0' 'foo foo foo'{,,,,} | sed -z '3,4s/foo/bar/2' | xxd
00000000: 666f 6f20 666f 6f20 666f 6f00 666f 6f20  foo foo foo.foo 
00000010: 666f 6f20 666f 6f00 666f 6f20 6261 7220  foo foo.foo bar 
00000020: 666f 6f00 666f 6f20 6261 7220 666f 6f00  foo.foo bar foo.
00000030: 666f 6f20 666f 6f20 666f 6f00            foo foo foo.

混合 \0 和 \n

里面echo $'foo\nfoo\nfoo' | sed -z 's/^foo/baz/2'还没有足够foo每行使更改第二个成为可能,但在下一个示例中:应该是吗?

$ printf 'foo foo foo\nfoo foo foo\n' | sed -z 's/^foo/baz/2'
foo foo foo
foo foo foo

foo哎呀,不,行首也没有足够的。问题是:一条线从哪里开始?在换行符处或在 a 处\0,或两者?或者没有?

^使用“-z”时考虑“行的开头”是没有意义的。

这是 sed 的内部混乱。请记住:使用-z是实验性的,可能会导致奇怪的问题。

模式空间

事实上,为了使替换正确工作,整个输入需要位于模式空间中。不,如果输入有 NUL ( ),则不起作用\0,这些将被视为行分隔符(或者,用 awk 的说法,作为记录分隔符)。

$ printf 'foo\0foo\0foo\0' | sed -z 's/^foo/baz/2'
foofoofoo

我们可以使用 sed 模式空间内的整个输入文件H;1h;$!d;x;.....,然后尝试^foo替换:

$ printf 'foo\0foo\0foo\0\n' | sed -z 'H;1h;$!d;x;l;s/^foo/ baz /M2'
foo\000foo\000foo\000\n$foo baz foo

允许l我们查看模式空间内的内容,并且M需要该标志,以便 比^第一行匹配更多。如果M不使用 , 则^foo仅在第一行(在模式空间的开头)匹配。

替代方案M是:

$ printf 'foo\0foo\0foo\0' | sed -z 'H;1h;$!d;x;l;s/\(^\|\x0\)foo/ baz /2'
foo\000foo\000foo$foo baz foo

请注意,尾随\0被删除,进入内部模式空间的是foo\000foo\000foo,它缺少尾随\0,而尾随是在输入中明确提供的。

我们可以\0通过添加尾随换行符来获取所有三个:

$ printf 'foo\0foo\0foo\0\n' | sed -z 'H;1h;$!d;x;l;s/\(^\|\x0\)foo/ baz /2'
foo\000foo\000foo\000\n$foo baz foo

这清楚地表明 sed\0有时将 a 视为分隔符,而\n在其他情况下将 a 视为分隔符。

简而言之,带有该-z选项的 sed 仍处于实验阶段。

答案3

在 Perl 中,有m正则表达式修饰符(当 slurping 时),但它没有帮助。

当然可以。

printf '%s\n' foo foo foo |\
perl -0777 -pe 's/^(foo)/++$c == 2 ? "bar" : $1/egm'

我们用 slurp -0777,匹配很多次g,并m帮助^在 slurp 中匹配,然后仅当计数器变量为 2 时e评估in 。bar

答案4

在 slurp 模式 (-z) 中,GNU sed 将记录分隔符视为空值。但由于 ASCII 文本文件中没有空字符,因此整个文件本质上是 sed 的一条记录或一行。为了解决这个问题,我们首先将所有换行符 (\n) 更改为行分隔符 (NUL),然后在第二个匹配上应用 s/// in t 多行模式。最后进行逆变换

printf '%s\n' foo foo foo |
sed -z '
  y/\n/\x00/
  s/^foo/BAR/M2
  y/\x00/\n/
'

输出:

foo
BAR
foo

相关内容