sed:捕获一个正则表达式重复的正则表达式组

时间:2019-07-29 00:49:43

标签: regex string bash sed regex-group

我有一些文件,如下例所示:

2000_A_tim110_may112_AATT_V22_P001_R1_001_V23_P007_R2_001_comb.ext
2000_BB_tim110_may112_AAMM_V14_P002_R1_001_V45_P008_R2_001_comb.ext
2000_C_tim110_DDFF_V18_P006_R1_001.ext
2000_DD_may112_EEJJ_V88_P004_R1_001.ext

从这些文件名中,我想提取前导2000_[A-Z]{1,2}和所有实例V[0-9]{2}的正则表达式模式。

也就是说,

来自

2000_A_tim110_may112_AATT_V22_P001_R1_001_V23_P007_R2_001_comb.ext

我想拥有

2000_A_V22_V23

并且来自

2000_DD_may112_EEJJ_V88_P004_R1_001.ext

我想拥有

2000_DD_V88

我一直在尝试通过sed实现这一目标,但到目前为止我还没有取得任何成功。

起初-天真的-我尝试过

find *.ext | sed -r 's/^(2000_[A-Z]{1,2}).*(V{1}[0-9]{2,3}).*(V{1}[0-9]{2,3}).*\.ext/\1_\2_\3/'

结果是:

2000_A_V22_V23
2000_BB_V14_V45
2000_C_tim110_DDFF_V18_P006_R1_001.ext
2000_DD_may112_EEJJ_V88_P004_R1_001.ext

这不是我想要的,因为这里的两个文件名未编辑返回。

然后,在阅读this post之后,我尝试将在中间捕获的组设为可选,如下所示:

find *.ext | sed -r 's/^(2000_[A-Z]{1,2}).*(V{1}[0-9]{2})?.*(V{1}[0-9]{2}).*\.ext/\1_\2_\3/'

但是自从返回以来,这似乎也不起作用

2000_A__V23
2000_BB__V45
2000_C__V18
2000_DD__V88

(即中间的捕获组似乎已被完全跳过。)

我的问题是,如何获得以下结果?

2000_A_V22_V23
2000_BB_V14_V45
2000_C_V18
2000_DD_V88

我要去哪里错了?或者相反,我想念什么?我是sedregex的新手,而且我想学习很好地使用两者,因此非常感谢指针和指导。

5 个答案:

答案 0 :(得分:3)

使用GNU awk进行FPAT:

$ awk -v FPAT='^2000_[A-Z]{1,2}|V[0-9]{2}' '{out=$1; for (i=2; i<=NF;i++) out=out "_" $i; print out}' file
2000_A_V22_V23
2000_BB_V14_V45
2000_C_V18
2000_DD_V88

答案 1 :(得分:1)

您可以将grep与循环一起使用:

for f in $(find 2000* -regex '2000_[A-Z].*ext'); do
    printf "%s\n" $(grep -Eo "^2000_[A-Z]{1,2}|_V[0-9]{2}" <<<"$f" | tr -d "\n")
done

答案 2 :(得分:0)

作为纯bash解决方案(对不起,没有sed),该怎么做:

#!/bin/bash

pat='((^2000_[A-Z]{1,2})|(_V[0-9]{2}))(.*)'
while IFS= read -r -d '' line; do
    result=
    while [[ $line =~ $pat ]]; do
        result+="${BASH_REMATCH[1]}"
        line="${BASH_REMATCH[4]}"
    done
    [[ -n "$result" ]] && echo "$result"
done < <(find . -type f -name '*.ext' -printf '%f\0')

输出:

2000_A_V22_V23
2000_BB_V14_V45
2000_C_V18
2000_DD_V88

答案 3 :(得分:0)

基本sed有什么难处?借助sed的替代功能,使用交替|运算符的功能。

$ cat sedtets 
2000_A_tim110_may112_AATT_V22_P001_R1_001_V23_P007_R2_001_comb.ext
2000_BB_tim110_may112_AAMM_V14_P002_R1_001_V45_P008_R2_001_comb.ext
2000_C_tim110_DDFF_V18_P006_R1_001.ext
2000_DD_may112_EEJJ_V88_P004_R1_001.ext

$ sed 's/\(2000_[A-Z]\{1,2\}\|_V[0-9]\+\)\|./\1/g' sedtets
2000_A_V22_V23
2000_BB_V14_V45
2000_C_V18
2000_DD_V88

DEMO

答案 4 :(得分:0)

正如我在comment中指出的那样,很难在sed中完成这项工作。但是,只要仔细使用分支和测试,就可以完成。

我使用的是经典的sed BRE表示法;如果您选择使用更现代但不一定是可移植的ERE表示法,则可以消除大量的反斜杠。我还将脚本保存在文件sed.script中,并将示例数据保存在文件data中,并使用以下命令运行命令:

$ sed -f sed.script data
2000_A_V22_V23
2000_BB_V14_V45
2000_C_V18
2000_DD_V88
$

脚本包含:

:retry
s/^\(2000_[A-Z]\{1,2\}\(_V[0-9][0-9]\)*\)_[^_]\{1,\}$/\1/
t
s/^\(2000_[A-Z]\{1,2\}\(_V[0-9][0-9]\)*\)_[^_]\{1,\}_/\1_/
t retry
  • 第一行设置标签retry
  • 第一行s///会查找2000_,后跟一个或两个大写字母,然后是一个零个或多个下划线,一个V和两位数字的实例(所有这些都被记住了) );然后是一个下划线和一个或多个非下划线的序列以及行尾。取而代之的是这些。
  • 如果第一个s///匹配,则分支到脚本的末尾(t,不带标签名称)。这样就可以打印该行。
  • 第二行s///与第一行非常相似,不同之处在于,它不查找行尾,而是在下划线和非下划线序列之后寻找另一个下划线。请注意,寻找_V##的术语(其中#代表数字)会找到尽可能多的术语,因此_xxx_术语与_V##_不匹配。取而代之的是记住的术语和下划线,因此它从字符串中删除了_xxx_的一个单元。
  • 如果第二个s///匹配,那么它将分支回到脚本的开头。
  • 从理论上讲,如果第二个s///不匹配,则循环将中断并打印剩余内容。实际上,样本数据无法达到此要求,但如果输入行根本不匹配(例如,它以2001而不是2000开头),则在不工作后将打印不变通过s///中的任何一个操作打开。
  • 如果应删除与起始模式不匹配的行,则可以通过在脚本开头添加一行来解决:

    /^2000_[A-Z]\{1,2\}/!d
    
  • 如果不包含任何_V##_序列的行也可以处理,可以在retry标签之前添加更多行。如果一行的末尾有_V##(并且很快就没了),那么它将跳过下一行。下一行在行中间查找_V##_,如果不匹配,则删除该行。

    /_V[0-9][0-9]$/b skip
    /_V[0-9][0-9]_/!d
    :skip
    

您可以通过在每个p操作之后添加s///来查看进度,该操作也显示了中间结果:

2000_A_may112_AATT_V22_P001_R1_001_V23_P007_R2_001_comb.ext
2000_A_AATT_V22_P001_R1_001_V23_P007_R2_001_comb.ext
2000_A_V22_P001_R1_001_V23_P007_R2_001_comb.ext
2000_A_V22_R1_001_V23_P007_R2_001_comb.ext
2000_A_V22_001_V23_P007_R2_001_comb.ext
2000_A_V22_V23_P007_R2_001_comb.ext
2000_A_V22_V23_R2_001_comb.ext
2000_A_V22_V23_001_comb.ext
2000_A_V22_V23_comb.ext
2000_A_V22_V23
2000_A_V22_V23
2000_BB_may112_AAMM_V14_P002_R1_001_V45_P008_R2_001_comb.ext
2000_BB_AAMM_V14_P002_R1_001_V45_P008_R2_001_comb.ext
2000_BB_V14_P002_R1_001_V45_P008_R2_001_comb.ext
2000_BB_V14_R1_001_V45_P008_R2_001_comb.ext
2000_BB_V14_001_V45_P008_R2_001_comb.ext
2000_BB_V14_V45_P008_R2_001_comb.ext
2000_BB_V14_V45_R2_001_comb.ext
2000_BB_V14_V45_001_comb.ext
2000_BB_V14_V45_comb.ext
2000_BB_V14_V45
2000_BB_V14_V45
2000_C_DDFF_V18_P006_R1_001.ext
2000_C_V18_P006_R1_001.ext
2000_C_V18_R1_001.ext
2000_C_V18_001.ext
2000_C_V18
2000_C_V18
2000_DD_EEJJ_V88_P004_R1_001.ext
2000_DD_V88_P004_R1_001.ext
2000_DD_V88_R1_001.ext
2000_DD_V88_001.ext
2000_DD_V88
2000_DD_V88

如果您的sed支持POSIX sed所需的扩展,则可以简化脚本。例如,如果可以使用|+,则可能会有一些选项可以简化脚本。这适用于任何版本的sed

此代码已经在macOS(BSD)sed和GNU sed上进行了测试,并且两者都相同。