如何从Unix中的文件名中提取具有已知结构的子串?

时间:2017-01-07 16:25:02

标签: awk grep

我有一堆包含大量信息的复杂文件名,我试图从每个文件名中提取两个子字符串。

名称具有以下结构:

???_S_????

从每个文件名中,我想在Unix shell中提取:

  1. 002_S_0295形式的字符串(例如002_S_115002_S_0729_I
  2. 以及241350与下一个下划线之间的数字(如291880334105,{{1}})
  3. 我尝试了一些grep和awk的组合,但实际上我无法提出解决方案。

5 个答案:

答案 0 :(得分:2)

任何支持ERE的sed:

$ sed -E 's/.*_([^_]+_S_[^_]+).*_I([^_]+).*/\1 \2/' file
002_S_0295 241350
002_S_1155 291880
002_S_0729 334105

任何POSIX sed:

$ sed 's/.*_\([^_]*_S_[^_]*\).*_I\([^_]*\).*/\1 \2/' file
002_S_0295 241350
002_S_1155 291880
002_S_0729 334105

使用GNU awk为第3个arg匹配():

$ awk 'match($0,/([^_]+_S_[^_]+).*_I([^_]+)/,a) { print a[1], a[2] }' file
002_S_0295 241350
002_S_1155 291880
002_S_0729 334105

答案 1 :(得分:0)

如果您将文件名存储在文件file中,则可以执行以下操作:

  

1.-形式的字符串??? S ???? (如002_S_0295,002_S_115,002_S_0729)

$ grep -Po '(?<=_)....S.....(?=_)' file
002_S_0295
002_S_1155
002_S_0729

这会提取__之间的所有字符,其格式为???? S ????? (注4?S5 ?,而你说3?S4?)。

  

2.-和_I与下一个下划线之间的数字(如241350,291880,334105)

同样,请使用grep外观:

$ grep -Po '(?<=_I)\d+' file
241350
291880
334105

答案 2 :(得分:0)

您可以在Bash中使用正则表达式(假设这是您的shell)来执行此操作:

while read -r line || [[ -n $line ]]; do 
    printf "'%s'\n" "$line"
    if [[ "$line" =~ (.{3}_S_.{4}) ]]
    then 
        echo ${BASH_REMATCH[1]}
    fi  
    if [[ "$line" =~ _I([0-9]+) ]]
    then
        echo ${BASH_REMATCH[1]}
    fi  
    echo
done <file

打印:

'ADNI_002_S_0295_MR_MT1__N3m_Br_20110623105302806_S110476_I241350_RightHippoSubfields.mgz.txt'
002_S_0295
241350

'ADNI_002_S_1155_MR_MT1__GradWarp__N3m_Br_20120322164018368_S97230_I291880_RightHippoSubfields.mgz.txt'
002_S_1155
291880

'ADNI_002_S_0729_MR_MT1__N3m_Br_20120913163818876_S159861_I334105_RightHippoSubfields.mgz.txt'
002_S_0729
334105

答案 3 :(得分:0)

awk -F'[_I]' '{print $3,$4,$5" "$(NF-1)}' OFS=_ file

002_S_0295 241350
002_S_1155 291880
002_S_0729 334105

答案 4 :(得分:0)

可以通过Bash中的扩展模式匹配来完成 - 诚然,这有点令人费解:

shopt -s extglob

patterns=('???_S_????' '_I+([!_])')

for fname in *.mgz.txt; do
    for pat in "${patterns[@]}"; do
        var=${fname#${fname%$pat*}}
        var=${var%${var##$pat}}
        echo "${var#_I}"
    done
done

这使用嵌套参数扩展来删除部分文件名。第一个文件和第一个模式的示例:

  • 在模式之前删除部分文件名:

    • ${fname%$pat*}扩展为${fname%???_S_????*},因此会删除从模式到名称末尾的所有内容,从而生成ADNI_
    • 此结果现在在${fname#${fname%$pat*}}中重复使用,后者变为${fname#ADNI_},扩展为

      002_S_0295_MR_MT1__N3m_Br_20110623105302806_S110476_I241350_RightHippoSubfields.mgz.txt
      

      所以var现在具有以模式开头的文件名部分。

  • 删除模式后的部分文件名:

    • ${var##$pat}扩展为${var##???_S_????},从文件名的开头删除模式。第一个模式不需要##(最长匹配),但第二个模式不需要{sup> 1 :+([!_])是“一个或多个非下划线字符”,我们想要的最长匹配。这种扩张的结果是

      6_S110476_I241350_RightHippoSubfields.mgz.txt
      

      ,即我们想删除的var部分。

    • ${var%${var##$pat}}扩展为

      ${var%6_S110476_I241350_RightHippoSubfields.mgz.txt}
      

      删除模式后的所有内容。

  • 打印结果:对于第一个模式,就是这个,我们可以直接打印第二个扩展,但第二个模式此时仍然包含_I,所以我们使用

    echo "${var#_I}"
    

    删除它。对于第一个模式,这是一个no-op 2 ,对于第二个模式,它删除了_I

所有这些的输出是

002_S_0295
241350
002_S_0729
334105
002_S_1155
291880

1 +()模式也是extglob所需的原因。

2 如果???_S_????恰好与 _I开头的字符串匹配,那么这会导致不必要的删除,但是在示例文件名上,它不会。