繁体   English   中英

如何使用 awk 重新排列列?

[英]How to rearrange the columns using awk?

我有一个包含 120 列的文件。 它的一部分在这里有 12 列。

A1      B1     C1      D1       A2      B2     C2      D2       A3      B3      C3      D3     
4       4       5       2       3       3       2       1       9       17      25      33
5       6       4       6       8       2       3       5       3       1       -1      -3
7       8       3       10      13      1       4       9       -3      -15     -27     -39
9       10      2       14      18      0       5       13      -9      -31     -53     -75
11      12      1       18      23      -1      6       17      -15     -47     -79     -111
13      14      0       22      28      -2      7       21      -21     -63     -105    -147
15      16      -1      26      33      -3      8       25      -27     -79     -131    -183
17      18      -2      30      38      -4      9       29      -33     -95     -157    -219
19      20      -3      34      43      -5      10      33      -39     -111    -183    -255
21      22      -4      38      48      -6      11      37      -45     -127    -209    -291

我想通过将所有 A 列(A1 A2 A3 A4)和类似的所有 B(B1 B2 B3 B4)、Cs(C1 C2 C3 C4)、Ds(D1 D2 D3 D4)放在一起来重新排列它。

我希望将列打印为

A1 A2 A3 A4 B1 B2 B3 B4 C1 C2 C3 C4 D1 D2 D3 D4
 

我的脚本是:

#!/bin/sh
sed -i '1d' input.txt
for i in {1..4};do
    j=$(( 1 + $(( 3 * $((  i - 1 )) ))  ))
awk '{print $'$j'}' input.txt >> output.txt
done
for i in {1..4};do
    j=$(( 2 + $(( 3 * $((  i - 1 )) ))  ))
awk '{print $'$j'}' input.txt >> output.txt
done
for i in {1..4};do
    j=$(( 3 + $(( 3 * $((  i - 1 )) ))  ))
awk '{print $'$j'}' input.txt >> output.txt
done

它在一列中打印所有内容。

是否只是 A,B,C,D,A,B,C,D 一路穿过? 像这样的东西应该工作:

awk '{
    for (i=0; i<4; ++i) {  # i=0:A, i=1:B,etc.
       for (j=0; 4*j+i<NF; ++j) {
         printf "%s%s", $(4*j+i+1), OFS;
       }
    }
    print ""
}'

这是两个通用方法解决方案,无需对 Input_file 中的字段编号进行硬编码,值可以按任何顺序排列,并且会自动对它们进行排序。 使用 GNU awk编写和测试,并带有示例。

第一种解决方案:遍历所有行及其各自的字段,然后按值排序以对标题执行索引。

awk '
FNR==1{
  for(i=1;i<=NF;i++){
     arrInd[i]=$i
  }
  next
}
{
  for(i=1;i<=NF;i++){
     value[FNR,arrInd[i]]=$i
  }
}
END{
  PROCINFO["sorted_in"]="@val_num_asc"
  for(i in arrInd){
     printf("%s%s",arrInd[i],i==length(arrInd)?ORS:OFS)
  }
  for(i=2;i<=FNR;i++){
     for(k in arrInd){
        printf("%s%s",value[i,arrInd[k]],k==length(arrInd)?ORS:OFS)
     }
  }
}
'   Input_file

或者,如果您想以表格格式获取 output,则在上述解决方案中进行小调整。

awk '
BEGIN { OFS="\t" }
FNR==1{
  for(i=1;i<=NF;i++){
    arrInd[i]=$i
  }
  next
}
{
  for(i=1;i<=NF;i++){
    value[FNR,arrInd[i]]=$i
  }
}
END{
  PROCINFO["sorted_in"]="@val_num_asc"
  for(i in arrInd){
    printf("%s%s",arrInd[i],i==length(arrInd)?ORS:OFS)
  }
  for(i=2;i<=FNR;i++){
    for(k in arrInd){
       printf("%s%s",value[i,arrInd[k]],k==length(arrInd)?ORS:OFS)
    }
  }
}
' Input_file | column -t -s $'\t'


第二种解决方案:与第一种解决方案几乎相同的概念,这里遍历条件内的数组,而不是在该程序的END块中显式调用它。

awk '
BEGIN { OFS="\t" }
FNR==1{
  for(i=1;i<=NF;i++){
    arrInd[i]=$i
  }
  next
}
{
  for(i=1;i<=NF;i++){
    value[FNR,arrInd[i]]=$i
  }
}
END{
  PROCINFO["sorted_in"]="@val_num_asc"
  for(i=1;i<=FNR;i++){
    if(i==1){
       for(k in arrInd){
          printf("%s%s",arrInd[k],k==length(arrInd)?ORS:OFS)
       }
    }
    else{
       for(k in arrInd){
          printf("%s%s",value[i,arrInd[k]],k==length(arrInd)?ORS:OFS)
       }
    }
  }
}
' Input_file | column -t -s $'\t'

与@MarkReed 类似的方法是操纵增量而不是测试条件,可以写成:

awk '{
  for (n=1; n<=4; n++)
    for (c=n; c<=NF; c+=4)
      printf "%s%s", ((c>1)?"\t":""), $c
    print ""
  }
' cols.txt

示例使用/输出

使用cols.txt中的示例输入,您将拥有:

$ awk '{
>   for (n=1; n<=4; n++)
>     for (c=n; c<=NF; c+=4)
>       printf "%s%s", ((c>1)?"\t":""), $c
>     print ""
>   }
> ' cols.txt
A1      A2      A3      B1      B2      B3      C1      C2      C3      D1      D2      D3
4       3       9       4       3       17      5       2       25      2       1       33
5       8       3       6       2       1       4       3       -1      6       5       -3
7       13      -3      8       1       -15     3       4       -27     10      9       -39
9       18      -9      10      0       -31     2       5       -53     14      13      -75
11      23      -15     12      -1      -47     1       6       -79     18      17      -111
13      28      -21     14      -2      -63     0       7       -105    22      21      -147
15      33      -27     16      -3      -79     -1      8       -131    26      25      -183
17      38      -33     18      -4      -95     -2      9       -157    30      29      -219
19      43      -39     20      -5      -111    -3      10      -183    34      33      -255
21      48      -45     22      -6      -127    -4      11      -209    38      37      -291

这是一个简洁的通用解决方案,它不受内存限制,就像 RavinderSing13 的解决方案一样。 (也就是说,它不会将整个输入存储在一个数组中以便在 END 中打印。)


BEGIN {
   OFS="\t" # output field separator
}

NR==1 {
   # Sort column titles
   for (i=1;i<=NF;i++) { sorted[i]=$i; position[$i]=i }
   asort(sorted)
   # And print them
   for (i=1;i<=NF;i++) { $i=sorted[i] }
   print
   next
}

{
   # Make an array of our input line...
   split($0,line)
   for (i=1;i<=NF;i++) { $i=line[position[sorted[i]]] }
   print
}

这里的想法是,在输入的第一行,我们记录输入中列的 position,然后使用asort()对列名列表进行排序。 重要的是,列名不能重复,因为它们被用作数组的索引。

当我们逐步浏览数据时,通过将每个字段替换为 position 中的值(按第一行排序)来重新排序每一行。

正确设置输入字段分隔符(空格、制表符、逗号等)并在每行中包含完整的字段集非常重要,否则 output 将出现乱码。

此外,这不会创建列。 您在问题中提到了 A4,但您的示例数据中没有 A4。 我们只对现有的进行排序。

最后,由于使用了asort() ,这是一个GNU awk程序。

Using any awk for any number of tags (non-numeric leading strings in the header line) and/or numbers associated with them in the header line, including different counts of each letter so you could have A1 A2 but then B1 B2 B3 B4,重现 output 中的输入顺序,并在 memory 中一次仅存储 1 行:

$ cat tst.awk
BEGIN { OFS="\t" }
NR == 1 {
    for ( fldNr=1; fldNr<=NF; fldNr++ ) {
        tag = $fldNr
        sub(/[0-9]+$/,"",tag)
        if ( !seen[tag]++ ) {
            tags[++numTags] = tag
        }
        fldNrs[tag,++numTagCols[tag]] = fldNr
    }
}
{
    out = ""
    for ( tagNr=1; tagNr<=numTags; tagNr++ ) {
        tag = tags[tagNr]
        for ( tagColNr=1; tagColNr<=numTagCols[tag]; tagColNr++ ) {
            fldNr = fldNrs[tag,tagColNr]
            out = (out=="" ? "" : out OFS) $fldNr
        }
    }
    print out
}

$ awk -f tst.awk file
A1      A2      A3      B1      B2      B3      C1      C2      C3      D1      D2      D3
4       3       9       4       3       17      5       2       25      2       1       33
5       8       3       6       2       1       4       3       -1      6       5       -3
7       13      -3      8       1       -15     3       4       -27     10      9       -39
9       18      -9      10      0       -31     2       5       -53     14      13      -75
11      23      -15     12      -1      -47     1       6       -79     18      17      -111
13      28      -21     14      -2      -63     0       7       -105    22      21      -147
15      33      -27     16      -3      -79     -1      8       -131    26      25      -183
17      38      -33     18      -4      -95     -2      9       -157    30      29      -219
19      43      -39     20      -5      -111    -3      10      -183    34      33      -255
21      48      -45     22      -6      -127    -4      11      -209    38      37      -291

或使用不同格式的标签和每个标签的不同列数:

$ cat file
foo1    bar1    bar2    bar3    foo2    bar4
4       4       5       2       3       3
5       6       4       6       8       2

$ awk -f tst.awk file
foo1    foo2    bar1    bar2    bar3    bar4
4       3       4       5       2       3
5       8       6       4       6       2

以上假设您希望每个标签的 output 顺序与输入顺序相匹配,而不是基于每个标签后的数值,因此如果您输入了A2 B1 A1那么 output 将是A2 A1 B1 ,而不是A1 A2 B1

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM