使用 awk 对文件中的特定列进行分组

使用 awk 对文件中的特定列进行分组

我将从 Sql 查询导出的数据放入文件中temp.log

以下是示例数据。标题行之后的条目按第一列排序。

另外,将节点 (36,54,69,76) 视为固定列表

<Name> <Status> <Node> <Requests>
AString1 Success 36 1
AString1 Success 54 4
AString1 Success 69 3
AString1 Success 76 4
BString2 Success 36 1
BString2 Success 54 2
BString2 Success 69 3
BString2 Success 76 1
CString3 Success 36 8
CString3 Success 54 7
CString3 Success 76 8
DString4 Success 36 8
DString4 Success 54 4
DString4 Success 69 12
DString4 Success 76 7

我需要如下输出

<Name> <Nodes> <Count of Requests>
Astring1 36 54 69 76 12
BString2 36 54 69 76 7
CString3 36 54 NA 76 23
DString4 36 54 69 76 31

这里最后一列(<Count of Requests>)表示该名称的每个节点上的请求总数。

NA如果节点没有收到任何请求,应该打印。(可以看到o/p中的CString3行,它缺少一行节点值69,在这种情况下它应该是NA,像CString3 36 54 NA 76 23

在成功案例 DString4 -DString4 36 54 69 76 31

这可以用 awk 命令来完成吗?

答案1

$ cat tst.awk
NR==1 {
    print "<Name>", "<Nodes>", "<Count of Requests>"
    next
}
$1 != prev {
    if ( NR > 2 ) {
        prt()
    }
    prev = $1
}
{
    reqs[$3]
    numReqs += $4
}
END { prt() }

function prt(   i, n, node, nodes) {
    n = split("36 54 69 76", nodes)

    printf "%s ", prev
    for (i=1; i<=n; i++) {
        node = nodes[i]
        printf "%s ", (node in reqs ? node : "NA")
    }
    print numReqs

    delete reqs
    numReqs = 0
}

$ awk -f tst.awk file
<Name> <Nodes> <Count of Requests>
AString1 36 54 69 76 12
BString2 36 54 69 76 7
CString3 36 54 NA 76 23
DString4 36 54 69 76 31

答案2

这是另一个 awk 解决方案:

$ awk '{if(NR==1){printf "%s\t%s\t%s\n", "<Name>","<Nodes>","<Count of Requests>"} else{nodes[$3]=1; a[$1][$3]++; sum[$1]+=$4}}END{for(string in a){ printf "%s\t", string; for(i in nodes){ (a[string][i]) ? val=i : val="NA"; printf "%s ",val }; printf "\t%d\n",sum[string];}}' file
<Name>  <Nodes> <Count of Requests>
CString3    36 54 NA 76     23
DString4    36 54 69 76     31
AString1    36 54 69 76     12
BString2    36 54 69 76     7
    

我在每个条目(“名称”、“节点”和“请求计数”)之间添加了一个制表符,以便您以后可以轻松地将它们分开。

这是更清晰形式的相同脚本(您仍然可以将其直接复制/粘贴到终端中):

awk '{ 
        ## If this is the first line
        if(NR==1){
            ## Set up the nodes array with the desired values
            nodes[36]=1;
            nodes[54]=1;
            nodes[69]=1;
            nodes[76]=1; 
            ## Print the header
            printf "%s\t%s\t%s\n", "<Name>","<Nodes>","<Count of Requests>"
        } 
        ## For all except the first line
        else{
            ## Save this node in the nodes array
            nodes[$3]=1;
            ## add this node to the values found for this string
            a[$1][$3]++; 
            ## add the number of requests
            sum[$1]+=$4
        }
     }
    ## After we have finished reading the file
    END{
        ## a holds all the first fields, the various strings
        for(string in a){ 
            ## Print the current string and a tab    
            printf "%s\t", string; 
            ## For each target node
            for(i in nodes){ 
                ## If this node has a value for this string, use the value. 
                ## If it does not, use "NA".
                (a[string][i]) ? val=i : val="NA"; 
                ## Print the value for the node
                printf "%s ",val 
            }; 
            ## Print the sum
            printf "\t%d\n",sum[string];
        }
    }' file

答案3

您可以使用两个索引数组来存储请求计数和每个名称的现有节点,并且在打印之前,您可以将现有节点数组与固定节点数组进行比较,例如:

awk 'BEGIN {
       totalNodes="36 54 69 76"
     }
     NR==1 {print "<Name> <Nodes> <Count of Requests>"};
     NR>1 {
         count[$1]+=$4; nodesTmp[$1]=nodesTmp[$1]" "$3;
         }
     END{
         for (i in count) {
             if(totalNodes!=nodesTmp[i]) {
                 split(totalNodes,tmp);
                 for(j in tmp){
                     if(nodesTmp[i]!~tmp[j]){
                         nodes[i]=nodes[i]" NA"
                     }
                     else {
                         nodes[i]=nodes[i]" "tmp[j]
                     }
                 }
             }
             else{
                 nodes[i]=nodesTmp[i]
             }; 
             print i, nodes[i], count[i] | "sort"
         };
     }' temp.log

答案4

假设 GNU awkforPROCINFO和真正的双索引数组,以下内容将起作用:

gawk 'BEGIN{PROCINFO["sorted_in"]="@ind_str_asc"}
      FNR>1{nodes[$3]=1; total[$1]+=$4; sgl_nodes[$1][$3]=1}
      END{
        printf "<Name> <Nodes> <Count of Requests>\n"
        for (f in sgl_nodes) {
          printf "%s%s",f,OFS;
          PROCINFO["sorted_in"]="@ind_num_asc";
          for (g in nodes) {
            if (sgl_nodes[f][g]) printf "%s%s",g,OFS; else printf "NA%s",OFS
          };
          PROCINFO["sorted_in"]="@ind_str_asc";
          printf "%d%s",total[f],ORS
        }
      }' temp.log 

这将在数​​组中注册实际遇到的节点编号nodes,以及与二维数组中的每个名称关联的节点编号,并以第一个索引sgl_nodes的值存储。在处理时,它还会在与该值对应的数组索引下对<Name>数组中的总请求计数进行求和。total<Name>

在文件末尾,我们首先打印标题行,然后迭代数组sgl_nodes以打印每个<Name>值( 的第一个索引sgl_nodes)的名称,然后迭代注册的所有节点nodes以打印节点号(如果遇到对于特定的<Name>) 或NA(如果该节点丢失),最后是总请求计数。

<Name>值并按升序打印,这要归功于PROCINFO在该BEGIN部分中执行的设置,该设置被覆盖为数字排序以迭代数组nodes

这种方法不依赖于temp.log被排序的条目,并且它自动识别节点号。


如果其中一个节点号恰好丢失,那么自动识别节点号的优势就会变成问题。全部条目。然而,既然您说过节点列表可以被认为是固定的,我们可以采取硬编码该列表来避免这个问题:

gawk 'BEGIN{PROCINFO["sorted_in"]="@ind_str_asc";split("36 54 69 76",nodes)}
      FNR>1{total[$1]+=$4; sgl_nodes[$1][$3]=1}
      END{
        printf "<Name> <Nodes> <Count of Requests>\n"
        for (f in sgl_nodes) {
          printf "%s%s",f,OFS;
          for (g=1;g<=4;g++) {
            if (sgl_nodes[f][nodes[g]]) printf "%s%s",nodes[g],OFS; else printf "NA%s",OFS
          };
          printf "%d%s",total[f],ORS
        }
      }' temp.log 

相关内容