【问题标题】:How to merge the lines starting with specific substring?如何合并以特定子字符串开头的行?
【发布时间】:2014-04-11 06:00:41
【问题描述】:

我有一个这样的文件

$ head test
                     gene=ENSECAG00000012421
                     note="synaptonemal complex central element protein 1
                     [Source:HGNC Symbol;Acc:28852]"
                     gene=ENSECAG00000017803
                     note="Uncharacterized protein
                     [Source:UniProtKB/TrEMBL;Acc:F6SNR9]"
                     gene=ENSECAG00000019088
                     note="cytochrome P450 2E1  [Source:RefSeq
                     peptide;Acc:NP_001104773]"
                     gene=ENSECAG00000004229

我想让这个文件看起来像这样

ENSECAG00000012421    synaptonemal complex central element protein 1 [Source:HGNC Symbol;Acc:28852]
ENSECAG00000017803    Uncharacterized protein [Source:UniProtKB/TrEMBL;Acc:F6SNR9]

我不确定 note 是否总是在两行中,所以我想要一些类似于

的内容
awk '{if(substr($1,1,4)=="gene") gene=$1; else print gene,$1}'

但我希望它能够识别它可能在两行中,并且单词之间也有空格。所以我希望它将“”中的所有内容打印为第 2 列(理想情况下用 \t 分隔 2 列,这样以后就不会混淆) 我知道如何去掉基因和注释和“,但不确定它们是否有助于识别。 我很高兴它是一串不同的命令,首先将整个音符放在一行中,然后将它与基因或所有内容一次性结合起来,无论哪种效果最好。

另外,如果您使用的是 awk,能否简要说明您在做什么?

感谢您的帮助!

【问题讨论】:

    标签: unix awk printf substr


    【解决方案1】:

    如果您有GNU awkmawk(该解决方案依赖于基于正则表达式的输入记录分隔符,严格符合POSIX 或更早的awk 实现不支持):

    短版:

    awk -v RS=' *(gene=|note="|")' '
      { gsub("\n", ""); if ($0 == "") next; $1=$1; 
        printf "%s%s", $0, (/^ENSECAG[0-9]+$/ ? "\t" : "\n") }
      ' file
    

    注释版:

    -v RS=' *(gene=|note="|")' - RS 是一个特殊变量,用于定义输入记录分隔符 - 指定一个正则表达式,将输入分成感兴趣的记录 - 跨行。

    awk -v RS=' *(gene=|note="|")' '
      {    
       gsub("\n", "");     # remove all newlines from record
       if ($0 == "") next  # ignore empty records
       $1=$1;              # rebuild record to compress multiple interior spaces
        # Output:
        #  - Is it a gene record, i.e. is there only 1 field that contains a gene name?
        #    Output it with just a trailing \t, but no trailing \n, so that the next
        #    note record will print on the same line.
        #  - Otherwise: a note record: print with trailing \n, effectively
        #    appending it to the previous gene record.
       printf "%s%s", $0, (/^ENSECAG[0-9]+$/ ? "\t" : "\n")
      }
      ' file
    

    【讨论】:

    • 感谢您的回复和解释!您的代码适用于注释跨越 2 行的行,但是当 note="processed_pseudogene" 时,它会变得混乱,例如/gene=ENSECAG00000005298 /note="processed_pseudogene" /gene=ENSECAG00000026864 /note="含1个环核苷酸结合域[来源:HGNC Symbol;Acc:26663]" 变成:ENSECAG00000005298 ENSECAG00000026864 含1个环核苷酸结合域[来源:HGNC符号;Acc:26663]
    • /gene=ENSECAG00000026236 /note="U6 spliceosomal RNA [Source:RFAM;Acc:RF00026]" 这也只有 2 行被正确处理 ENSECAG00000026236 U6 spliceosomal RNA [Source:RFAM;Acc:RF00026 ]
    • @user3400409:我的代码错误地假设任何仅包含 1 个字段的记录根据定义都是基因行。解决方案是检查该记录的内容,以确定它是否是基因名称。我不知道该测试可以/应该有多具体,但我已经更新了我的答案,用基于正则表达式的特定测试替换了过于广泛的测试(NF==1)。注意:原始的非空行测试 /\S/ 莫名其妙地不适用于旧版 gawk/mawk 中的单行注释记录,所以我不得不稍微重写 awk 程序;希望它现在对你有用。
    • 谢谢!还有另一个错误,但那是我的错,因为我没有意识到我的 /note 也可以跨越 3 行,一旦我的测试文件 grep 正确,你的命令也可以完美地用于 3 行!此外,我所有的基因名称都以 ENSECAG 开头,后跟数字,因此对于我的示例来说已经足够宽泛了。干杯!
    • @user3400409:我的荣幸;我很高兴我们最终解决了问题。
    【解决方案2】:

    可能过于复杂,但这里有一种方法:

    /^\s*gene=/  { gene=substr($1, 6) }
    /^\s*note=/  { note=substr($0, 28) }
    /"$/         { if (substr($1,1,4)=="note")
                     print gene, substr($0, 28, length($0)-28);
                   else
                     print gene, note, substr($0, 22, length($0)-22) }
    

    请注意,这同时处理单行和两行注释。

    【讨论】:

    • 这适用于特定的输入(除了 OP 想要一个 \t 而不是第一个输出标记后的空格),但不能很好地概括;您可以使用 [[:space:]] 而不是 \s 来使其更便携。
    • @mklement0,您的回答(我非常喜欢)也是针对“特定输入”(例如,它包含“note”和“gene”这两个词)。
    • 谢谢(我希望我的回答适用于所有awks,但事实并非如此);我指的是您答案中的硬编码字符位置。
    【解决方案3】:

    使用 awk

    awk 'BEGIN{FS="\n";RS="gene="}{gsub(/(note=|\")/,"");print $1,$2,$3}' file|awk '$1=$1'
    
    ENSECAG00000012421 synaptonemal complex central element protein 1 [Source:HGNC Symbol;Acc:28852]
    ENSECAG00000017803 Uncharacterized protein [Source:UniProtKB/TrEMBL;Acc:F6SNR9]
    ENSECAG00000019088 cytochrome P450 2E1 [Source:RefSeq peptide;Acc:NP_001104773]
    ENSECAG00000004229
    

    【讨论】:

    • 关闭,但 OP 想要一个 \t 将基因名称与注释分开(即,只有 2 输出列,由 \t 分隔)。由于使用了多字符,因此需要 gawkmawkRS.
    【解决方案4】:
     sed -n 'N;;/"$/!N;s/\n//g;p' input | \
       sed 's/.*gene=//;s/[ \t]*note="\([^"]*\)"/\t\1 /;s/  */ /g'
    

    给予:

    ENSECAG00000012421  synaptonemal complex central element protein 1 [Source:HGN...
    ENSECAG00000017803  Uncharacterized protein [Source:Uni...
    ENSECAG00000019088  cytochrome P450 2E1 [Source:Ref...
    

    【讨论】:

      【解决方案5】:
      $ awk '{$1=$1; gsub(/"/,""); sub(/^note=/,""); pfx=(sub(/^gene=/,"")?(NR>1?ORS:""):OFS); printf "%s%s",pfx,$0} END{print ""}' file
      ENSECAG00000012421 synaptonemal complex central element protein 1 [Source:HGNC Symbol;Acc:28852]
      ENSECAG00000017803 Uncharacterized protein [Source:UniProtKB/TrEMBL;Acc:F6SNR9]
      ENSECAG00000019088 cytochrome P450 2E1 [Source:RefSeq peptide;Acc:NP_001104773]
      ENSECAG00000004229
      

      【讨论】:

      • 关闭,但 OP 想要一个 \t 将基因名称与注释分开(即,只有 2 输出列,由 \t 分隔)。
      猜你喜欢
      • 2016-09-28
      • 2017-09-20
      • 2022-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-16
      • 1970-01-01
      相关资源
      最近更新 更多