【问题标题】:awk combine unique values of other columns based on the unique values of a single columnawk 根据单列的唯一值组合其他列的唯一值
【发布时间】:2020-03-29 10:05:27
【问题描述】:

我的输入文件看起来像

Item1,200,a,four,five,six,seven,eight1,nine1
Item2,500,b,four,five,six,seven,eight2,nine2
Item3,900,c,four,five,six,seven,eight3,nine3
Item2,800,d,four,five,six,seven,eight4,nine4
Item1,,e,four,five,six,seven,eight5,nine5

基于第一列的唯一值,我想组合所有其他列的唯一值。 到目前为止我尝试的是:

awk -F, '{
a[$1]=a[$1]?a[$1]"_"$2:$2;
b[$1]=b[$1]?b[$1]"_"$3:$3;
c[$1]=c[$1]?c[$1]"_"$4:$4;
d[$1]=d[$1]?d[$1]"_"$5:$5;
e[$1]=e[$1]?e[$1]"_"$6:$6;
f[$1]=f[$1]?f[$1]"_"$7:$7;
g[$1]=g[$1]?g[$1]"_"$8:$8;
h[$1]=h[$1]?h[$1]"_"$9:$9;
}END{for (i in a)print i, a[i], b[i], c[i], d[i], e[i], f[i], g[i], h[i];}' OFS=, input.txt

上面的输出是:

Item3,900,c,four,five,six,seven,eight3,nine3
Item1,200_,a_e,four_four,five_five,six_six,seven_seven,eight1_eight5,nine1_nine5
Item2,500_800,b_d,four_four,five_five,six_six,seven_seven,eight2_eight4,nine2_nine4

但我期待的是:

Item3,900,c,four,five,six,seven,eight3,nine3
Item1,200,a_e,four,five,six,seven,eight1_eight5,nine1_nine5
Item2,500_800,b_d,four,five,six,seven,eight2_eight4,nine2_nine4

我正在寻求一些帮助:

  1. 如何在组合值时只取唯一值?
  2. 只要存在空白值,在组合时不应在末尾附加分隔符(在我上面的例子中为下划线)?
  3. 如何根据第 1 列的值对输出进行排序?

非常感谢您的帮助。

【问题讨论】:

  • 当您说“唯一值”时 - 您是指给定 $1 的唯一值还是给定 $1 的给定字段的唯一值?例如,如果值“十”出现在 Item1 的 2 个不同字段中 - 它会在输出中出现两次还是只出现一次?
  • @EdMorton 对于给定的 1 美元的给定字段,它是唯一的。含义:对于每个唯一的 Item 值(即 col-1),连接每列下存在的唯一值
  • 好的,谢谢澄清。连接唯一值的顺序是否重要?例如,如果Item1 行上的最后一个值输出是nine5_nine1 而不是nine1_nine5,可以吗?
  • @EdMorton 在 col-1 上排序就足够了。对于剩余的列,任何值的顺序都可以。

标签: csv awk


【解决方案1】:

任何awk加上sort

$ cat tst.awk
BEGIN { FS=OFS="," }
{
    key = $1
    keys[key]
    for (i=2; i<=NF; i++) {
        if ( ($i ~ /[^[:space:]]/) && (!seen[key,i,$i]++) ) {
            idx = key FS i
            vals[idx] = (idx in vals ? vals[idx] "_" : "") $i
        }
    }
}
END {
    for (key in keys) {
        printf "%s%s", key, OFS
        for (i=2; i<=NF; i++) {
            idx = key FS i
            printf "%s%s", vals[idx], (i<NF ? OFS : ORS)
        }
    }
}

.

$ awk -f tst.awk file | sort -t, -k1,1
Item1,200,a_e,four,five,six,seven,eight1_eight5,nine1_nine5
Item2,500_800,b_d,four,five,six,seven,eight2_eight4,nine2_nine4
Item3,900,c,four,five,six,seven,eight3,nine3

或使用 GNU awk 用于数组的数组(参见 https://www.gnu.org/software/gawk/manual/gawk.html#Multidimensionalhttps://www.gnu.org/software/gawk/manual/gawk.html#Arrays-of-Arrays 了解两者之间的区别)和 sorted_in(参见 https://www.gnu.org/software/gawk/manual/gawk.html#Controlling-Array-Traversalhttps://www.gnu.org/software/gawk/manual/gawk.html#Controlling-Scanning):

$ cat tst.awk
BEGIN { FS=OFS="," }
{
    for ( i=2; i<=NF; i++ ) {
        vals[$1][i][$i]
    }
}
END {
    PROCINFO["sorted_in"] = "@ind_str_asc"
    for ( key in vals ) {
        printf "%s%s", key, OFS
        for ( i=2; i<=NF; i++ ) {
            sep = ""
            for ( val in vals[key][i] ) {
                if ( val ~ /[^[:space:]]/ ) {
                    printf "%s%s", sep, val
                    sep = "_"
                }
            }
            printf "%s", (i<NF ? OFS : ORS)
        }
    }
}

.

$ awk -f tst.awk file
Item1,200,a_e,four,five,six,seven,eight1_eight5,nine1_nine5
Item2,500_800,b_d,four,five,six,seven,eight2_eight4,nine2_nine4
Item3,900,c,four,five,six,seven,eight3,nine3

【讨论】:

  • 感谢@Ed Morton 的脚本。您能否解释一下您的脚本中发生的逻辑?作为 awk 语法的新手,尤其是数组概念似乎很棘手且超级强大,我试图理解:1. What's happening with seen[key,i,$i] --&gt; is it like the $i value is being added to 'seen' array at index location [key,i]...? Isn't it becoming multi-dimensional array by doing so? 2. How does "@ind_str_asc" help in sorting the records based on col-1 value?
  • 每当您在 awk 脚本中看到一个名为 seen[] 的数组时,它习惯性地用于一个目的 - 测试在该点用作其索引的任何内容之前是否发生过。它总是在像seen[foo]++ 这样的测试+后增量的上下文中使用,因为第一次出现“foo”时,seen[foo] 的值为零(错误条件),然后将其递增设置为 1,以便第二次“ foo" 出现 seen[foo] 的值非零(真实条件)。所以if (!seen[key,i,$i]++)if the index created from key, i, and $i has not previously occurred
  • 我添加了指向 GNU 手册的链接,该手册将解释伪多维数组(所有 awk 都支持)和数组数组(即真正的多维数组)之间的区别(仅支持GNU awk)以及预定义的排序字符串的含义。如果您不确定正在做什么或变量有什么值,您可以随时在整个代码中添加“打印”语句,以告诉您希望所有变量的值发生。
【解决方案2】:

编辑:添加具有更合理变量名称的解决方案。

awk '
BEGIN{
  FS=OFS=","
}
{
  first_field_value[$1]
  for(i=2;i<=NF;i++){
    if($i!=""){
      split(field_values[$1,i],temp_array,"_")
      delete column_value
      for(p in temp_array){
        column_value[temp_array[p]]
      }
      if(!($i in column_value)){
        (field_values[$1,i] == "" ? "" : field_values[$1,i] "_")$i
      }
    }
  }
  tot_field=tot_field>NF?tot_field:NF
}
END{
  for(ind in first_field_value){
    printf "%s,",ind;
    for(j=2;j<=tot_field;j++){
      printf("%s%s",field_values[ind,j],j==tot_field?ORS:OFS)
    }
  }
}
'  Input_file

输出如下。

Item3,900,c,four,five,six,seven,eight3,nine3
Item1,200,a_e,four,five,six,seven,eight1_eight5,nine1_nine5
Item2,500_800,b_d,four,five,six,seven,eight2_eight4,nine2_nine4

解释:这是我之前代码的解释;它的变量名称不太合理,但仍然可以阅读此解释以供理解。

awk '                                          ##Starting awk program from here.
BEGIN{                                         ##Starting BEGIN section.
  FS=OFS=","                                   ##Setting FS and OFS as comma here.
}
{
  b[$1]                                        ##Creating array b which has index $1, basically to keep track of $1 values as index here.
  for(i=2;i<=NF;i++){                          ##Running for loop from i=2 to till value of NF here.
    if($i!=""){                                ##Checking if any field is NOT NULL then do following.
      num=split(c[$1,i],d," ")                 ##Splitting array c with index of $1,i and splitting its value to array d; it also saves number of elements in array d to variable num here.
      for(p=1;p<=num;p++){                     ##Running a for loop from p=1 to value of num.
        e[d[p]]                                ##Creating array e whose index is value of array d which are actually values of fields and I am making sure duplicate values will NOT come by this array.
      }
      if(!($i in e)){                          ##If current field is not present in array e then do following.
        a[$1,i]=(a[$1,i]?a[$1,i] "_":"")$i     ##Creating array a with index of $1,i and keep concatenating its value to it.
      }
      c[$1,i]=(c[$1,i]?c[$1,i] OFS:"")$i       ##Creating array c with current field value and keep concatenating it; array c is the one which STOPS values to re-enter OR let us say it DO NOT allow duplicates values in array a.
    }
  }
  tot_field=tot_field>NF?tot_field:NF          ##Creating variable tot_field which will let us know till what value we need to run loop in END BLOCK of this code.
}
END{
  for(k in b){                                     ##Starting a for loop which traverse through array b here.
    printf "%s,",k;                                ##Printing its index here which is basically first field of all lines.
    for(j=2;j<=tot_field;j++){                     ##Running for loop till value of Maximum field value.
      printf("%s%s",a[k,j],j==tot_field?ORS:OFS)   ##Printing value of array a whose index is  k and j where k is index of array b(1st field) and j is field number starts from 2.
    }
  }
}
'  Input_file                                      ##Mentioning Input_file name here.

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2015-10-10
  • 2015-05-30
  • 1970-01-01
  • 1970-01-01
  • 2021-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多