如何在 sed 中 post- 处理捕获组同时保留其他捕获组?
How can I post-process a capture group while preserving others in sed?
我有一个包含 3 个捕获组的字符串,我想保留第一个和第三个,但对第二个进行替换。我如何在 sed 中表达它?
具体来说,我有一个输入字符串:
top-level.subpath.one.subpath.two.subpath.forty-five
而且我想保留第一个.
之前的部分,将中间部分缩短到每个单词的第一个字母,并保留最后一个.
之后的部分。结果应如下所示:
top-level.s.o.s.t.s.forty-five
为了保留捕获组,我有:
sed -r 's/([^.]*)(.*)(\..*)/.../'
这让我:
top-level....forty-five
为了将 .subpath.one.subpath.two.subpath
之类的东西转换为仅首字母,我有:
sed -r 's/(\.[^.])[^\.]*//g'
这让我:
.s.o.s.t.s
我想基本上应用第二个 sed 表达式来捕获组 2。有什么方法可以链接 sed 替换以仅对第二个捕获组执行第二个替换,同时保留第一个和第三个?
您可以使用
sed -E ':a; s/^(.*\.[^.])[^.]+(\.)//; ta' file > newfile # GNU sed
sed -E -e :a -e 's/^(.*\.[^.])[^.]+(\.)//' -e ta file > newfile # FreeBSD sed
见online demo。 详情:
-E
- 启用 POSIX ERE 语法(+
现在是一个 一个或多个 量词, (...)
被解析作为分组结构)
:a
- 设置一个 a
标签
s/^(.*\.[^.])[^.]+(\.)//
- 找到零个或多个字符,一个 .
然后是 .
以外的任何单个字符(将其捕获到第 1 组),然后是一个或多个其他字符比 .
,然后匹配并捕获到第 2 组一个点字符,匹配被替换为串联的第 1 组 + 第 2 组值
ta
- 成功替换后转到 a
标签。
一个简单的 awk
解决方案,适用于任何版本的 awk,包括 MacOS:
s='top-level.subpath.one.subpath.two.subpath.forty-five'
awk 'BEGIN{FS=OFS="."} {for(i=2;i<NF;++i) $i=substr($i,1,1)}1' <<< "$s"
top-level.s.o.s.t.s.forty-five
此awk
命令使用.
作为输入和输出字段分隔符。我们遍历字段位置 2
到 last-1
并将每个字段的值替换为该字段的第一个字符。最后打印完整记录。
一个 BSD sed
解决方案做同样的事情:
sed -E -e ':x' -e 's/(.+\..)[^.]+\././; tx' <<< "$s"
top-level.s.o.s.t.s.forty-five
这可能适合您 (GNU sed):
sed -E ':a;s/(\..*)\B.(.*\.)//;ta' file
捕获第一个和最后一个句点并挖空中间部分,删除任何并排的单词字符。
改善@anubhava 的 sed 回答:
sed -E 's/(\..)[^.]+\././g;s//./g' file
使用全局标志并重复相同的替换提供了 2 个命令的解决方案。
为了回答您的问题,根本不可能对单个子表达式捕获的 string/group 执行 运行 命令。
我会使用这个 sed 来完成你的任务:
sed -E 's/(\.[^.])[^.]+\././g; s/(\.[^.])[^.]+\././g'
- 两个
s
命令相同,但必须 运行 两次以修改相邻字段,因为模式以(文字)点开头和结尾以避免匹配第一个和最后一个字段.
- FWIW,这在基准测试中一直比
sed -E -e :goto -e 's/(\.[^.])[^.]+\././; t goto'
快 - 使用 GNU sed 快 15%,使用 busybox sed 快 25%。
还有这种使用 GNU sed 的方法:
sed -E 's/([^.])[^.]*\././2g'
Ng
在第 N 个匹配项开始全局替换。
- 这是 GNU sed 中记录的功能,但是 POSIX 表示将数字与
g
组合会产生未定义的行为。
- 如果您需要保留的不仅仅是第一个字段,它可能会很有用。
使用 GNU awk gensub()
:
$ awk 'BEGIN{FS=OFS="."} {beg=; end=$NF; [=10=]=gensub(/([^.])[^.]+/,"\1","g"); =beg; $NF=end} 1' file
top-level.s.o.s.t.s.forty-five
我有一个包含 3 个捕获组的字符串,我想保留第一个和第三个,但对第二个进行替换。我如何在 sed 中表达它?
具体来说,我有一个输入字符串:
top-level.subpath.one.subpath.two.subpath.forty-five
而且我想保留第一个.
之前的部分,将中间部分缩短到每个单词的第一个字母,并保留最后一个.
之后的部分。结果应如下所示:
top-level.s.o.s.t.s.forty-five
为了保留捕获组,我有:
sed -r 's/([^.]*)(.*)(\..*)/.../'
这让我:
top-level....forty-five
为了将 .subpath.one.subpath.two.subpath
之类的东西转换为仅首字母,我有:
sed -r 's/(\.[^.])[^\.]*//g'
这让我:
.s.o.s.t.s
我想基本上应用第二个 sed 表达式来捕获组 2。有什么方法可以链接 sed 替换以仅对第二个捕获组执行第二个替换,同时保留第一个和第三个?
您可以使用
sed -E ':a; s/^(.*\.[^.])[^.]+(\.)//; ta' file > newfile # GNU sed
sed -E -e :a -e 's/^(.*\.[^.])[^.]+(\.)//' -e ta file > newfile # FreeBSD sed
见online demo。 详情:
-E
- 启用 POSIX ERE 语法(+
现在是一个 一个或多个 量词,(...)
被解析作为分组结构):a
- 设置一个a
标签s/^(.*\.[^.])[^.]+(\.)//
- 找到零个或多个字符,一个.
然后是.
以外的任何单个字符(将其捕获到第 1 组),然后是一个或多个其他字符比.
,然后匹配并捕获到第 2 组一个点字符,匹配被替换为串联的第 1 组 + 第 2 组值ta
- 成功替换后转到a
标签。
一个简单的 awk
解决方案,适用于任何版本的 awk,包括 MacOS:
s='top-level.subpath.one.subpath.two.subpath.forty-five'
awk 'BEGIN{FS=OFS="."} {for(i=2;i<NF;++i) $i=substr($i,1,1)}1' <<< "$s"
top-level.s.o.s.t.s.forty-five
此awk
命令使用.
作为输入和输出字段分隔符。我们遍历字段位置 2
到 last-1
并将每个字段的值替换为该字段的第一个字符。最后打印完整记录。
一个 BSD sed
解决方案做同样的事情:
sed -E -e ':x' -e 's/(.+\..)[^.]+\././; tx' <<< "$s"
top-level.s.o.s.t.s.forty-five
这可能适合您 (GNU sed):
sed -E ':a;s/(\..*)\B.(.*\.)//;ta' file
捕获第一个和最后一个句点并挖空中间部分,删除任何并排的单词字符。
改善@anubhava 的 sed 回答:
sed -E 's/(\..)[^.]+\././g;s//./g' file
使用全局标志并重复相同的替换提供了 2 个命令的解决方案。
为了回答您的问题,根本不可能对单个子表达式捕获的 string/group 执行 运行 命令。
我会使用这个 sed 来完成你的任务:
sed -E 's/(\.[^.])[^.]+\././g; s/(\.[^.])[^.]+\././g'
- 两个
s
命令相同,但必须 运行 两次以修改相邻字段,因为模式以(文字)点开头和结尾以避免匹配第一个和最后一个字段. - FWIW,这在基准测试中一直比
sed -E -e :goto -e 's/(\.[^.])[^.]+\././; t goto'
快 - 使用 GNU sed 快 15%,使用 busybox sed 快 25%。
还有这种使用 GNU sed 的方法:
sed -E 's/([^.])[^.]*\././2g'
Ng
在第 N 个匹配项开始全局替换。- 这是 GNU sed 中记录的功能,但是 POSIX 表示将数字与
g
组合会产生未定义的行为。 - 如果您需要保留的不仅仅是第一个字段,它可能会很有用。
使用 GNU awk gensub()
:
$ awk 'BEGIN{FS=OFS="."} {beg=; end=$NF; [=10=]=gensub(/([^.])[^.]+/,"\1","g"); =beg; $NF=end} 1' file
top-level.s.o.s.t.s.forty-five