【问题标题】:Join lines with the same value in the first column连接第一列中具有相同值的行
【发布时间】:2013-11-06 22:14:38
【问题描述】:

我有一个包含三列的制表符分隔文件(摘录):

AC147602.5_FG004    IPR000146   Fructose-1,6-bisphosphatase class 1/Sedoheputulose-1,7-bisphosphatase
AC147602.5_FG004    IPR023079   Sedoheptulose-1,7-bisphosphatase
AC148152.3_FG001    IPR002110   Ankyrin repeat
AC148152.3_FG001    IPR026961   PGG domain

我想使用 bash 来获得它:

AC147602.5_FG004 IPR000146 Fructose-1,6-bisphosphatase class 1/Sedoheputulose-1,7-bisphosphatase IPR023079 Sedoheptulose-1,7-bisphosphatase
AC148152.3_FG001 IPR023079 Sedoheptulose-1,7-bisphosphatase IPR002110   Ankyrin repeat IPR026961    PGG domain

因此,如果第一列中的 ID 在多行中相同,则应该为每个 ID 生成一行,并将行的所有其他部分连接起来。在示例中,它将给出两行文件。

【问题讨论】:

  • @oberlies,有时可以将标签添加到涵盖答案中使用但未在问题中提及的技术的问题。这就是其中一种情况,尤其是当替代方法是创建新的元标记时。
  • @close-voters:这个问题怎么可能太宽泛了?答案是一行 awk 脚本。

标签: bash awk


【解决方案1】:

试试这个单线:

 awk -F'\t' -v OFS='\t' '{x=$1;$1="";a[x]=a[x]$0}END{for(x in a)print x,a[x]}' file

【讨论】:

    【解决方案2】:

    无论出于何种原因,awk 解决方案在 cygwin 中对我不起作用。所以我改用 Perl。它围绕一个制表符连接并用 \n

    分隔行
    cat FILENAME | perl -e 'foreach $Line (<STDIN>) { @Cols=($Line=~/^\s*(\d+)\s*(.*?)\s*$/); push(@{$Link{$Cols[0]}}, $Cols[1]); } foreach $List (values %Link) { print join("\t", @{$List})."\n"; }'
    

    【讨论】:

      【解决方案3】:

      将取决于文件大小(和 awk 限制)

      如果太大,这将通过首先对文件进行排序来减少对 awk 的需求,并且只在内存中保留 1 个 标签 用于打印

      使用整行修改的带有后期打印的经典版本

      sort YourFile \
       | awk '
            last==$1 { sub( /^[^[:blank:]]*[[:blank:]]+/, ""); C = C " " $0; next}
            NR > 1 { print Last C; Last = $1; C = ""}
            END { print Last}
            '
      

      另一个使用字段和预印本但不太“人类可读”的版本

      sort YourFile \
       | awk '
            last!=$1 {printf( "%s%s", (! NR ? "\n" : ""), Last=$1)}
            last==$1 {for( i=2;i<NF;i++) printf( " %s", $i)}
            '
      

      【讨论】:

        【解决方案4】:

        纯 bash 版本。它没有其他依赖项,但需要 bash 4.0 或更高版本 (2009) 才能支持关联数组。

        全部在一条线上:

        { declare -A merged; merged=(); while IFS=$'\t' read -r key value; do merged[$key]="${merged[$key]}"$'\t'"$value"; done; for key in "${!merged[@]}"; do echo "$key${merged[$key]}"; done } < INPUT_FILE.tsv
        

        可读和注释等效:

        {
          # Define `merged` as an empty associative array.
          declare -A merged
          merged=()
        
          # Read tab-separated lines. Any leftover fields also end up in `value`.
          while IFS=$'\t' read -r key value
          do
            # Append to any value that's already there, separated by a tab.
            merged[$key]="${merged[$key]}"$'\t'"$value"
          done
        
          # Loop over the input keys. Note that the order is arbitrary;
          # pipe through `sort` if you want a predictable order.
          for key in "${!merged[@]}"
          do
            # Each value is prefixed with a tab, so no need for a tab here.
            echo "$key${merged[$key]}"
          done
        } < INPUT_FILE.tsv
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-06-17
          • 2011-09-18
          • 2017-01-08
          • 2013-03-03
          • 2015-07-17
          • 2020-03-15
          • 2017-09-25
          相关资源
          最近更新 更多