基于唯一 ID 转置数据 - awk

Transposing data based on unique ID - awk

我真的希望你能帮上忙。我是 (g)awk 的新手,过去两周我一直在与它作斗争。

我的原始文件如下 - 有一列具有唯一 ID,另一列具有唯一名称。随后的列是各种课程,每个字段包含(当不为空时)每门课程和每个学生的标记。所以每个学生每门课程只有一个分数:

Id  Name        Course1 Course2 Course3 Course4 Course5
1   John           55
2   George                                         63
4   Alex                          64
1   John                                   74
3   Emma           63
2   George                64
4   Alex                                   60
2   George         29                   
3   Emma                                           69
1   John                  67
3   Emma                  80
4   Alex           57
2   George                                 91
1   John                          81
1   John                                           34
3   Emma                          75
2   George                        89
4   Alex                                           49
3   Emma                                   78
4   Alex                  69
5   TERRY                 67
6   HELEN                         39 

这就是我想要实现的 - 根据唯一 ID 转置数据,即标记,并将标记放在每个相应课程的下方,如下所示:

Id  Name        Course1 Course2 Course3 Course4 Course5
1   John          55      69       64     60      49
2   George        29      64       89     91      63
3   Emma          63      80       75     78      69
4   Alex          57      69       64     60      49
5   TERRY                 67
6   HELLEN                         39

这是我到目前为止设法得到的:

Id  Name        Course1 Course2 Course3 Course4 Course5
1   John          55            
2   George        29            
3   Emma          63            
4   Alex          57    
5   TERRY
6   HELLEN      
1   John                  69            
2   George                64            
3   Emma                  80            
4   Alex                  69            
5   TERRY                 67
6   HELLEN
1   John                           64
2   George                         89
3   Emma                           75
4   Alex                           64
5   TERRY
6   HELLEN                         39
                                        ...and so on

根据我在 awk 上已经知道的知识来实现​​对我来说真的有点棘手(请注意,我对基于 sed/perl e.t.c. 的解决方案不感兴趣)。 如果要提供一些帮助(最好不是单行),我可能会要求描述一下,因为我对解决方案和方法本身都很感兴趣。

非常感谢任何帮助。

编辑 这是我为到达最后阶段(以及卡住的地方)而编写的代码

#!/bin/bash

files3="*.csv"
for j in $files3
do
    #echo "processing $j..."
    fi13=$(awk -F" " '(NR==1){field13=;}{print field13}' ./work1/test1YA.csv)
    fi14=$(awk -F" " '(NR==1){field14=;}{print field14}' ./work1/test1YA.csv)
    fi15=$(awk -F" " '(NR==1){field15=;}{print field15}' ./work1/test1YA.csv)
    fi16=$(awk -F" " '(NR==1){field16=;}{print field16}' ./work1/test1YA.csv)

#   awk -F" " 'BEGIN{OFS=" ";RS="\n"}{print ,,,,,,,,,,,}' "$j" >> ./work1/test2YA.csv
    awk -F" " -v f13="$fi13" -v f14="$fi14" -v f15="$fi15" -v f16="$fi16" '{if(==f13){=;===""}if(==f14){=;===""}if(==f15){=;===""}if(==f16){=;===""}{print ,,,,,,,,,,,,,,,}}' "$j" >> ./work1/test2YA.csv

done;

awk -F" " 'BEGIN{print "ID","Title","FirstName","MiddleName","LastName","FinalMarks","Status","Username","Campus","Code","Programme","Year","course1","course2","course3","course4"}{print}' ./work1/test2YA.csv >> ./work1/test3YA.csv

这是 gnu awk 的解决方案:

course.awk

BEGIN { # setup field width for constant field splitting
        FIELDWIDTHS = "2 2 12 7 1 7 1 7 1 7 1 7"
        # setup sort order (by id)
        PROCINFO["sorted_in"] = "@ind_num_asc"
      }

NR == 1 { # print header
          print
          next
        }

      {
        # add ids to names
        names[  ] = 

        # store under id and course number the mark if it is present
        for( c = 1; c <= 5; c++ ) {
          field = 2+ (c*2)
          if( $(field) !~ /^ *$/ ) {
            marks[ , c ] = $(field)
          }
        }
      }

END   {
        # output
        for( id in names ) {
          printf("%-4s%-12s%7s %7s %7s %7s %7s\n",id, names[ id ], marks[ id, 1], marks[ id, 2], marks[ id, 3], marks[ id, 4], marks[ id, 5])
        }
      }

这样使用:awk -f course.awk your_file.

输入不是制表符分隔,而是固定列宽的事实有点不雅:

  • 使用 FIELDWIDTHS%Ns,其中 N 从 FIELDWIDTHS
  • 派生
  • FIELDWIDTHS 考虑到 ID 和名称、Course1 和 Course2 之间的空列,...
  • 检查是否存在标记:if( $(field) !~ /^ *$/ ) 检查字段是否不完全由空格组成。

这可能是 awk 中的近似值:

NR==1{
    for(x=1;x<=NF;x++)
    {
        head=head $x"\t";
    }
    print head
}
NR>1{
    for(i=3;i<=NF;i++)
    {
        students["\t"]=students["\t"] "\t"$i;
    }
}
END{
    for (stu in students)
    {
        print stu,students[stu];
    }
}

Id      Name    Course1 Course2 Course3 Course4 Course5
5       TERRY   67
4       Alex    64      60      57      49      69
1       John    55      74      67      81      34
6       HELEN   39
3       Emma    63      69      80      75      78
2       George  63      64      29      91      89

相同的想法,也许更简单

$ awk 'BEGIN{ FIELDWIDTHS="16 8 8 8 8 8"} 
       NR==1{print;next} 
        NR>1{keys[]; 
             for(i=2;i<=6;i++) 
                {gsub(" ","",$i); 
                 if($i) a[,i]=$i}} 
         END{for(k in keys) 
                {printf "%16s",k; 
                 for(i=2;i<=6;i++) printf "%-8s",a[k,i]; 
                 print ""}}' file


Id  Name        Course1 Course2 Course3 Course4 Course5
3   Emma        63      80      75      78      69
4   Alex        57      69      64      60      49
6   HELEN                       39
5   TERRY               67
1   John        55      67      81      74      34
2   George      29      64      89      91      63

您也可以通过管道对输出进行排序 sort -n

... | sort -n

Id  Name        Course1 Course2 Course3 Course4 Course5
1   John        55      67      81      74      34
2   George      29      64      89      91      63
3   Emma        63      80      75      78      69
4   Alex        57      69      64      60      49
5   TERRY               67
6   HELEN                       39

使用 GNU awk 用于 FIELDWIDTHS、二维数组和 sorted_in:

$ cat tst.awk
NR==1 {
    print
    split([=10=],f,/\S+\s*/,s)
    for (i=1;i in s;i++) {
        w[i] = length(s[i])
        FIELDWIDTHS = FIELDWIDTHS (i>1?" ":"") w[i]
    }
    next
}
{
    sub(/\s*$/,"  ")
    for (i=1;i<=NF;i++) {
        if ($i ~ /\S/) {
            val[][i] = $i
        }
    }
}
END {
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (id in val) {
        for (i=1;i<=NF;i++) {
            printf "%*s", w[i], val[id][i]
        }
        print ""
    }
}

.

$ awk -f tst.awk file
Id  Name        Course1 Course2 Course3 Course4 Course5
1   John            55      67      81      74     34
2   George          29      64      89      91     63
3   Emma            63      80      75      78     69
4   Alex            57      69      64      60     49
5   TERRY                   67
6   HELEN                           39

这是我对此的看法。这适用于普通的 awk(不使用 FIELDWIDTHS),它会自动调整到不同数量的字段(即添加一个 Course7 列,你应该没问题)。此外,您可以将它指向多个文件,它应该分别处理每个文件。

#!/usr/bin/awk -f

# Initialize variables on the first record of each input file
# (and also print the header)
#
FNR <= 1 {
  print
  delete name
  delete score
  next
}

# Process each line.
#
{
  id = substr([=10=], 0, 16)    #
  name[id]                 # Store the unique identifier in an array
  pos = 0                  #

  # Step through the score fields until we hit the end of the line,
  # storing scores in another array.
  do {
    score[id, pos] += substr([=10=],17+pos*8,8) +0
    printf("id='%s' pos=%s value=%s total=%s\n", id, pos, substr([=10=],17+pos*8,8)+0, score[id, pos] );
  } while (17+(++pos)*8 < length())
}

# Keep track of our maximum number of fields
pos>max { max=pos }

# Finally, generate our (randomly sorted) output.
END {
  for (id in name) {        # Step through the records...
    printf("%-12s", id);
    for (i=0; i<max; i++) { # Step through the fields...
      if (score[id, i]==0) score[id, i]=""
      printf("%-8s", score[id, i]);
    }
    printf("\n")
  }
}

它有点长,但我认为更容易理解它的作用。