awk 命令从标题包含特定子字符串的 csv 文件中提取列

awk 命令从标题包含特定子字符串的 csv 文件中提取列

我有一个巨大的 csv 文件,其格式为

aaa1, "aaa2, aa214", aa21, "aa, a14", aa211, aa44, aaa445 
data, data, data, data, data, data, data,
........................................
........................................

我想提取标题包含特定字符串的列,比如说a2.对于上面的示例,这包括列aaa2等等aa21

我尝试过的 awk 命令是

awk --csv 'NR==1 {for (i=1; i<=NF; i++) if ($i ~ /a2/) print $i}' file.csv 

但这只返回匹配的标题,而不返回它们下面的列。请指出我正确的方向。我使用的是Linux系统。

答案1

mlr确实支持这种伪造的 CSV 格式,并且可以基于正则表达式剪切字段:

$ mlr --csv --csv-trim-leading-space --allow-ragged-csv-input cut -rf a2 your-file.csv
"aaa2, aa214",aa21,aa211
data,data,data

不过,这不会扩展到内存不适合的 CSV。为了--allow-ragged-csv-input应对每行中字段数量与示例中不同的 CSV,在任何情况下都必须完整读取文件,以了解有多少个列(没有标题的列会自动分配数字标题) )。

答案2

使用 GNU awkFPAT并假设字段不包含换行符:

awk -v FPAT='[^,]*|\\s*("([^"]|"")*")\\s*' -v OFS=',' '
    NR==1 {
        for ( inFldNr=1; inFldNr<=NF; inFldNr++ ) {
            if ( $inFldNr ~ /a2/ ) {
                out2in[++numOutFlds] = inFldNr
            }
        }
    }
    {
        for ( outFldNr=1; outFldNr<=numOutFlds; outFldNr++ ) {
            inFldNr = out2in[outFldNr]
            printf "%s%s", $inFldNr, (outFldNr<numOutFlds ? OFS : ORS)
        }
    }
' file.csv
 "aaa2, aa214", aa21, aa211
 data, data, data

我没有使用--csv(也需要 GNU awk),因为您的输入文件不是有效的 CSV(,s 和第一个"s 之间有空格,并且由于,第二行末尾的尾随而具有比标题更多的数据列),因此不应期望 CSV 解析器能够处理它。另外,即使您解决了这个问题,--csv也会从每个列标题周围去掉引号,我猜您想保留它们,当并非所有字段都用双引号引起来时,这会有点问题。仅当字段可以包含换行符并且您无论如何都想从字段周围删除引号时,使用--csv才比使用正确的设置明显更好。FPAT

如果你确实想尝试--csv那么这个(未经测试的)可能对你有用:

awk --csv -v OFS=',' '
    NR==1 {
        for ( inFldNr=1; inFldNr<=NF; inFldNr++ ) {
            if ( $inFldNr ~ /a2/ ) {
                out2in[++numOutFlds] = inFldNr
            }
        }
    }
    {
        for ( outFldNr=1; outFldNr<=numOutFlds; outFldNr++ ) {
            inFldNr = out2in[outFldNr]
            outVal = $inFldNr
            if ( outVal ~ ("[" OFS ORS "\"]") ) {
                gsub(/"/,"\"\"",outVal)
                outVal = "\"" outVal "\""
            }
            printf "%s%s", outVal, (outFldNr<numOutFlds ? OFS : ORS)
        }
    }
' file.csv

但是没有简单的方法可以告诉"在循环中添加 s 时哪些前导/尾随空格(如果有的话)最初位于引号内还是引号外,因此我只是将整个字段用引号括起来。

使用 awk 高效解析 csv 的最稳健方法是什么有关使用 awk 解析 CSV 的更多信息。

答案3

使用(以前称为 Perl_6)

...使用 Raku 的Text::CSV模块:

~$ raku -MText::CSV -e 'csv(in => csv(in => $*IN, sep => ", "), out => $*OUT);'  <  file

上面将把 CSV 文件(所有列)读入内存。该文件通过 std-in 接收,所有列都通过std-out$*IN输出。$*OUT请注意自定义", "字段分隔符。

要过滤特定列(删除所有其他列),请使用 Raku 的grepkey:k参数,该参数返回任何找到的列的数字索引:

~$ raku -MText::CSV -e 'my  @aoa = csv(in => $*IN, sep => ", ");
                        my  @col-nbrs = @aoa[0].grep(/a2/, :k);
                        for @aoa.map( *.[@col-nbrs]) {
                            .map(q["] ~ * ~ q["]).join("\t").put
                        };'  <  file

输入示例:

aaa1_a, "aaa2, aa214_b", aa21_c, "aa, a14_d", aa211_e, aa44_f, aaa445_g
data1_a, data1_b, data1_c, data1_d, data1_e, data1_f, data1_g,
data2_a, data2_b, data2_c, data2_d, data2_e, data2_f, data2_g,

示例输出(TSV,来自上面):

"aaa2, aa214_b" "aa21_c"    "aa211_e"
"data1_b"   "data1_c"   "data1_e"
"data2_b"   "data2_c"   "data2_e"

上面包含与“a2”匹配的所有列名称。为了使输出更具可读性,每个字段的文本都用引号引起来,然后在选项卡join上进行编辑\t。不加引号,代码就简化了不少。下面的列被连接\t\t并输出:

~$ raku -MText::CSV -e 'my  @aoa = csv(in => $*IN, sep => ", ");
                        my  @col-nbrs = @aoa[0].grep(/a2/, :k);
                        for @aoa.map( *.[@col-nbrs]) {
                            $_.join("\t\t").put
                        };'  <  file
aaa2, aa214_b       aa21_c      aa211_e
data1_b     data1_c     data1_e
data2_b     data2_c     data2_e

最后,您可以利用 的Text::CSV输出功能,默认情况下: 1.逗号join上的列,,以及 2. 包含空格的双引号字段。

% raku -MText::CSV -e 'my  @aoa = csv(in => $*IN, sep => ", ");
                       my  @col-nbrs = @aoa[0].grep(/a2/, :k);
                       my  @filtered; for @aoa.map( *.[@col-nbrs] ) {
                           @filtered.push($_); 
                       };  csv(in => @filtered, out => $*OUT);'  <  file
"aaa2, aa214_b",aa21_c,aa211_e
data1_b,data1_c,data1_e
data2_b,data2_c,data2_e

https://raku.land/zef:Tux/Text::CSV
https://github.com/Tux/CSV/blob/master/doc/Text-CSV.md
https://docs.raku.org
https://raku.org

答案4

另一个非常方便的工具是鸭数据库。跑步

duckdb --csv -c "SELECT COLUMNS('.*a2.*') from read_csv_auto('input.csv',HEADER = true)" >output.csv

你得到

AAA2,AAA214 AA21 AA211
数据 数据 数据
数据 数据 数据

相关内容