【问题标题】:Using awk array values as column indexes使用 awk 数组值作为列索引
【发布时间】:2018-03-08 05:05:51
【问题描述】:

数据:

EMAIL,NAME,KEY,LOCATION
foo@gmail.com,Joe,ABC,Denver
bar@gmail.com,Jane,EFD,Denver
...

总体目标: 接受我关心的字段并生成多个文件的脚本,其中包含数据中的所有唯一列。例如:

myScript.sh NAME LOCATION

生产:

Joe_Denver.csv - contains all lines with "Joe" and "Denver" in the
NAME and LOCATION columns
Jane_Denver.csv - contains all lines with "Jane" and "Denver" in the NAME and LOCATION columns

到目前为止我所拥有的:

  • Bash 脚本接受任意数量的字段并将其存储在一个数组中
  • 查找字段的列索引号并将其存储在数组中

我正在尝试:

  • 使用 AWK 获取索引数组,然后输出我指定的字段的所有唯一组合,然后将其存储在数组中
  • 遍历该字段组合数组,为每个组合打印一个文件,该文件包含数据中在这些列中具有这些值的所有行

我的第一步的 AWK 命令看起来像:

awk -F, -v colIdxs="${bashIdxs[*]}" '!seen[$colIdxs[*]]++ {print $colIdxs[*]}'

也就是说,我希望将存储在bashIdxs 中的索引用作 awk 脚本中的列索引(其中 bashIdxs 可以是任意大小)。

如何做到这一点?此外,如果有更好的方法来完成我正在尝试做的事情(我相信有),我也很想知道出于好奇。

【问题讨论】:

    标签: awk


    【解决方案1】:

    未经测试,但如果不完全正确,将接近:

    colNames="$*"
    awk -v colNames="$colNames" '
    BEGIN {
        split(colNames,tmp)
        for (i in tmp) {
            names[tmp[i]]
        }
        FS=OFS=","
    }
    NR==1 {
        for (i=1; i<=NF; i++) {
            if ($i in names) {
                f[++nf] = $i
            }
        }
        hdr = $0
        next
    }
    {
        out = ""
        for (i=1; i<=nf; i++) {
            out = (out=="" ? "" : out "_") $(f[i])
        }
        out = out ".csv"
        if ( !seen[out]++ ) {
            print hdr > out
        }
        print > out
    }
    ' file
    

    如果您不使用 GNU awk 并收到“打开的文件过多”错误,则需要将 print &gt; out 更改为 print &gt;&gt; out; close(out)

    【讨论】:

    • 谢谢!您介意注释其中一些行的含义吗?我是 AWK 的新手,发现我真的跟不上。特别是:names[tmp[i]] - 这是做什么的?稍后:if ($i in names) { - $i 是列号;这是否意味着 names[tmp[i]] 存储所有列号? f[++nf] = $i; - 创建存储的列号数组? if ( !seen[out]++ ) { print hdr > out }
    • 抱歉,我没有时间这样做,但如果您尝试关注并阅读手册页,然后对其中的任何部分有任何具体问题,我将很乐意回答。
    • 抱歉,误按了“进入”按钮,现在5分钟后我就不能编辑了……
    • names[tmp[i]] 填充由存储在tmp[] 中的值索引的names[],因此names[] 最终存储了所有名称字符串的集合,例如"NAME"。是的,f[++nf] = $i 创建了一个数组,将所需的输出列号映射到输入列号。 !seen[foo]++ 是第一次出现 foo 时做某事的常见 awk 习惯用法。在这种情况下,需要将标题行打印到新的输出文件中。
    • 还有:print > out 会自动打印 $0 吗?
    【解决方案2】:

    awk 来救援!

    $ awk -F, -v cols='NAME,LOCATION' '
            NR==1 {for(i=1;i<=NF;i++) if(FS cols FS ~ FS $i FS) sel[i]; h=$0; next}
                  {key=""; 
                   for(i=1;i<=NF;i++) if(i in sel) key=(key==""?$i:key"_"$i); file=key".csv"; 
                   if(!(key in header)) {print h > file; header[key]} 
                   print > file}' file
    

    给予

    $ head *_*.csv
    ==> Jane_Denver.csv <==
    EMAIL,NAME,KEY,LOCATION
    bar@gmail.com,Jane,EFD,Denver
    
    ==> Joe_Denver.csv <==
    EMAIL,NAME,KEY,LOCATION
    foo@gmail.com,Joe,ABC,Denver
    

    注意。如果为您的操作系统打开了太多文件(基于输入数据和唯一键的数量),您可能需要关闭文件...

    【讨论】:

    • 谢谢! if(FS cols FS ~ FS $i FS) - 这是做什么的?我意识到这是一个正则表达式比较,但“cols”是从哪里来的?如果我是对的,这会将它与“,colNumber”进行比较,对吗?
    • cols='NAME,LOCATION' 是作为输入给出的列。检查文件中的任何列是否与给定的列选择匹配。
    • 我太傻了,我错过了设置变量的那部分。谢谢!
    • 关于if(FS cols FS ~ FS $i FS) 的另一个问题,假设我有字段“NAME”和“LAST_NAME”,但我只想匹配“NAME”,正则表达式是否只匹配 NAME?这背后的逻辑是什么?
    • 是的,它将匹配所提供的任何内容。如果只想匹配NAME,只需输入cols='NAME',如果有多个,请提供逗号分隔的列表,如示例所示。
    猜你喜欢
    • 2021-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-17
    • 2020-07-12
    相关资源
    最近更新 更多