使用 `sed` 查找重复模式
find recurring pattern with `sed`
我正在使用 GNU bash 4.3.48
我预计
echo "23S62M1I19M2D" | sed 's/.*\([0-9]*M\).*//g'
会输出 62M19M... 但它不会。
sed 's/\([0-9]*M\)//g'
删除所有 [0-9]*M
并检索 23S1I2D。但是小组 </code> 没有像我想象的那样工作。</p>
<p><code>sed 's/.*\([0-9]*M\).*/ /g'
, 检索 M...
我做错了什么?
谢谢!
您的替换可能有效,但不是您认为的那样。
在替换 s/\(foo...\)//
中,</code> 匹配任何 <code>\(...\)
匹配和捕获的内容,因此您的替换将 foo...
替换为 foo...
!
% echo "1234ABC" | sed 's/\([A-Z]\)/--/'g
1234-A--B--C-
因此您需要匹配 更多,但只捕获匹配的一部分。例如:
echo "23S62M1I19M2D" | sed 's/[0-9]*[A-LN-Z]*\([0-9]*M\)//g'
62M19M2D
在 sed 's/.*\([0-9]*M\).*//g'
的情况下(出现在对问题的编辑中,还是我只是错过了它?),.*
匹配“贪婪”——它匹配的次数与它可能可以,因此包括 M
之前的数字。在上面的例子中,要求 [A-LN-Z]
位于未捕获部分的末尾,因此数字必须与捕获内的 [0-9]
匹配。
在编写或解释正则表达式时,清楚地了解“贪婪”的含义是一个非常重要的想法。
如果您知道您只会遇到后缀 S
、M
、I
和 D
,另一种方法是明确删除您不希望遇到的组合我想要:
echo "23S62M1I19M2D" | sed 's/[0-9]\+[SID]//g'
这给出了预期的:
62M19M
更新: 此变体产生相同的输出,但拒绝所有非数字、非 M
后缀:
echo "23S62M1I19M2D" | sed 's/[0-9]\+[^0-9M]//g'
问题是 .*
是贪心的。由于只有 M
是必须的,当引擎找到最后一个 M
时,它满足正则表达式,因此匹配所有字符串, M
被捕获并因此在替换为 </code> 后保留反向引用。</p>
<p>这意味着,您无法使用 <code>sed
轻松做到这一点。你可以用 Perl 更容易地做到这一点,因为它支持匹配和跳过模式:
#!/bin/bash
perl -pe 's/\d+M(*SKIP)(*F)|.//g' <<< "23S62M1I19M2D"
见online demo。模式匹配
\d+M(*SKIP)(*F)
- 一个或多个数字,M
,然后省略匹配,从失败位置开始搜索下一个匹配
|.
- 或匹配换行符以外的任何字符。
或者简单地匹配所有匹配项并将它们连接起来:
perl -lane 'BEGIN{$a="";} while (/\d+M/g) {$a .= $&} END{print $a;}' <<< "23S62M1I19M2D"
所有 \d+M
匹配都附加到 $a
变量,该变量在处理字符串结束时打印。
使用您展示的示例和 awk
您可以尝试以下程序。
echo "23S62M1I19M2D" |
awk '
{
val=""
while(match([=10=],/[0-9]+M/)){
val=val substr([=10=],RSTART,RLENGTH)
[=10=]=substr([=10=],RSTART+RLENGTH)
}
print val
}
'
解释: 简单的解释是,使用 echo
打印值并将其作为标准输入发送到 awk
程序。在 awk
程序中使用其 match
函数匹配其中提到的正则表达式 (/[0-9]+M
) 运行 循环查找每一行中的所有匹配项并在最后打印收集的匹配值每行。
这可能对你有用 (GNU sed):
sed -nE '/[0-9]*M/{s//\n&\n/g;s/(^|\n)[^\n]*\n?//gp}' file
用换行符包围匹配项,然后删除不匹配的部分。
替代方案,使用 grep 和 tr:
grep -o '[0-9]*M' file | tr -d '\n'
N.B。 tr
删除所有换行符(包括最后一个)恢复最后一个换行符,使用:
grep -o '[0-9]*M' file | tr -d '\n' | paste
备用解决方案会将所有结果连接成一行。要使用第一个解决方案获得相同的结果,请使用:
sed -nE '/[0-9]*M/{s//\n&\n/g;s/(^|\n)[^\n]*\n?//g;H};${x;s/\n//gp}' file
我正在使用 GNU bash 4.3.48
我预计
echo "23S62M1I19M2D" | sed 's/.*\([0-9]*M\).*//g'
会输出 62M19M... 但它不会。
sed 's/\([0-9]*M\)//g'
删除所有 [0-9]*M
并检索 23S1I2D。但是小组 </code> 没有像我想象的那样工作。</p>
<p><code>sed 's/.*\([0-9]*M\).*/ /g'
, 检索 M...
我做错了什么?
谢谢!
您的替换可能有效,但不是您认为的那样。
在替换 s/\(foo...\)//
中,</code> 匹配任何 <code>\(...\)
匹配和捕获的内容,因此您的替换将 foo...
替换为 foo...
!
% echo "1234ABC" | sed 's/\([A-Z]\)/--/'g
1234-A--B--C-
因此您需要匹配 更多,但只捕获匹配的一部分。例如:
echo "23S62M1I19M2D" | sed 's/[0-9]*[A-LN-Z]*\([0-9]*M\)//g'
62M19M2D
在 sed 's/.*\([0-9]*M\).*//g'
的情况下(出现在对问题的编辑中,还是我只是错过了它?),.*
匹配“贪婪”——它匹配的次数与它可能可以,因此包括 M
之前的数字。在上面的例子中,要求 [A-LN-Z]
位于未捕获部分的末尾,因此数字必须与捕获内的 [0-9]
匹配。
在编写或解释正则表达式时,清楚地了解“贪婪”的含义是一个非常重要的想法。
如果您知道您只会遇到后缀 S
、M
、I
和 D
,另一种方法是明确删除您不希望遇到的组合我想要:
echo "23S62M1I19M2D" | sed 's/[0-9]\+[SID]//g'
这给出了预期的:
62M19M
更新: 此变体产生相同的输出,但拒绝所有非数字、非 M
后缀:
echo "23S62M1I19M2D" | sed 's/[0-9]\+[^0-9M]//g'
问题是 .*
是贪心的。由于只有 M
是必须的,当引擎找到最后一个 M
时,它满足正则表达式,因此匹配所有字符串, M
被捕获并因此在替换为 </code> 后保留反向引用。</p>
<p>这意味着,您无法使用 <code>sed
轻松做到这一点。你可以用 Perl 更容易地做到这一点,因为它支持匹配和跳过模式:
#!/bin/bash
perl -pe 's/\d+M(*SKIP)(*F)|.//g' <<< "23S62M1I19M2D"
见online demo。模式匹配
\d+M(*SKIP)(*F)
- 一个或多个数字,M
,然后省略匹配,从失败位置开始搜索下一个匹配|.
- 或匹配换行符以外的任何字符。
或者简单地匹配所有匹配项并将它们连接起来:
perl -lane 'BEGIN{$a="";} while (/\d+M/g) {$a .= $&} END{print $a;}' <<< "23S62M1I19M2D"
所有 \d+M
匹配都附加到 $a
变量,该变量在处理字符串结束时打印。
使用您展示的示例和 awk
您可以尝试以下程序。
echo "23S62M1I19M2D" |
awk '
{
val=""
while(match([=10=],/[0-9]+M/)){
val=val substr([=10=],RSTART,RLENGTH)
[=10=]=substr([=10=],RSTART+RLENGTH)
}
print val
}
'
解释: 简单的解释是,使用 echo
打印值并将其作为标准输入发送到 awk
程序。在 awk
程序中使用其 match
函数匹配其中提到的正则表达式 (/[0-9]+M
) 运行 循环查找每一行中的所有匹配项并在最后打印收集的匹配值每行。
这可能对你有用 (GNU sed):
sed -nE '/[0-9]*M/{s//\n&\n/g;s/(^|\n)[^\n]*\n?//gp}' file
用换行符包围匹配项,然后删除不匹配的部分。
替代方案,使用 grep 和 tr:
grep -o '[0-9]*M' file | tr -d '\n'
N.B。 tr
删除所有换行符(包括最后一个)恢复最后一个换行符,使用:
grep -o '[0-9]*M' file | tr -d '\n' | paste
备用解决方案会将所有结果连接成一行。要使用第一个解决方案获得相同的结果,请使用:
sed -nE '/[0-9]*M/{s//\n&\n/g;s/(^|\n)[^\n]*\n?//g;H};${x;s/\n//gp}' file