【问题标题】:clean up the header of csv files with awk用 awk 清理 csv 文件的头部
【发布时间】:2017-06-03 23:22:31
【问题描述】:

我有一堆看起来像这样的 csv 文件:

Time,Channel A
(s),(V)

0.00000000,0.58058560
0.00666667,0.58913630
0.01333334,0.58058560

我想将其转换为:

Time (s),Channel A (V)
0.00000000,0.58058560
0.00666667,0.58913630
0.01333334,0.58058560

文件中的单位不一定相同(即可能有(ms),(µV) 等)

我已经用awk 'FNR!=3' 删除了空白行(始终是第 3 行),但是剪切第二行参数并将它们粘贴到第一行字段后面超出了我的能力;(

请帮帮我! 动机:有很多这些文件。手工做是不可行的……

编辑: 首先:感谢您帮助我。由于您的两个答案似乎存在相同的问题,因此我的文件中可能存在问题?我怀疑空行会以某种方式造成麻烦?

  • shell 脚本给了我错误'xpected empty line, read '
  • perl 脚本 awk 脚本给我一个错误的第一行:

     (V) (s),Channel A
    0.00000000,0.58058560
    

如果有帮助,I uploaded a sample infile

【问题讨论】:

    标签: linux bash shell csv awk


    【解决方案1】:
    awk -F , -v RS='\r\n' 'FNR == 1 { x = $1; y = $2 } FNR == 2 { print x " " $1 "," y " " $2 } FNR > 3'
    

    简要说明:awk 接受逻辑表达式作为范围模式(范围内的任何内容都可以使用),因此 FNR == n 仅表示该模式适用于当前文件中的行号 n ;最后一个模式适用于所有之后行#3;省略该操作意味着简单地将输入打印为已读。因此,第 1 行和第 2 行被特殊处理,每行都根据自己的含义,而第 3 行被忽略,因为它没有匹配的模式。

    更新:根据@123 的建议编辑为设置RS(记录分隔符)。另一种方法是将其包含在脚本本身中,可能与FS(字段分隔符)一起包含,如下所示:

    awk 'BEGIN { FS=","; RS="\r\n" } FNR == 1 { x = $1; y = $2 } FNR == 2 { print x " " $1 "," y " " $2 } FNR > 3'
    

    【讨论】:

    • 虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高​​答案的长期价值。
    • 这个解决方案也给了我一个错误的第一行,我认为这也是由回车引起的(参见 perl-answer 或 bash-answer)。 但是我会给你这个:如果这行得通,我会喜欢它,因为我似乎明白它的作用,只是看它! ;) 你能试着修复它吗?
    • @Paul 只需添加一个标志将RS 设置为\r\n,即awk -vRS="\r\n" -F, 'yadayada' file
    • @Paul 我担心它可能会以同样的方式失败。应该可以修复它——一方面,您可以尝试@123 的建议;如果做不到这一点,您能否发布文本文件前几行的十六进制转储,例如cat file.txt | head -n 5 | hexdump -C
    • 知道了,现在应该修好了。正如在其他 cmets 中提到的,issue(DOS 样式)行终止。
    【解决方案2】:

    如果您决定继续使用 bash,这里有一个脚本将为您执行错误检查

    #!/usr/bin/env bash
    
    function Error() { 1>&2 echo "$@"; }
    
    function cleanup_csv() {
        IFS=$',\r' read -a Header || { Error "could not read header"; return 1; }
        IFS=$',\r' read -a Units || { Error "could not read units"; return 1; }
        declare -r NCols=${#Header[@]}
        declare -r n_units=${#Units[@]}
    
        [[ $NCols -eq $n_units ]] || {
            Error "number of columns in Header ($NCols) not equal to Units ($n_units)"
            return 1
        }
    
        if IFS=$' \t\r' read -a words; then
            if [[ $(echo "${words[*]}") ]]; then
                Error "expected empty line, read '${words[*]}'"
                return 1
            fi
        else
            Error "could not read line 3, expected empty line"
            return 1
        fi
    
        local i= sep=
        for ((i=0; i<NCols; ++i)); do
            printf "%s" "$sep${Header[i]} ${Units[i]}"
            sep=,
        done
        printf "\n"
        cat
    }
    
    cleanup_csv "$@"
    

    给定一个包含您的示例输入的文件

    Time,Channel A
    (s),(V)
    
    0.00000000,0.58058560
    0.00666667,0.58913630
    0.01333334,0.58058560
    

    并像这样调用

    ./cleanup_csv.sh < in
    

    它产生所需的输出

    Time (s),Channel A (V)
    0.00000000,0.58058560
    0.00666667,0.58913630
    0.01333334,0.58058560
    

    我还测试了两列以上

    Time,Channel A,Channel B
    (s),(V),(mV)
    
    0.00000000,0.58058560,12.34
    0.00666667,0.58913630,3.1415
    0.02000002,0.58058560,0.913
    

    这也很有效

    Time (s),Channel A (V),Channel B (mV)
    0.00000000,0.58058560,12.34
    0.00666667,0.58913630,3.1415
    0.02000002,0.58058560,0.913
    

    编辑

    我更新了空行的读取和检测,因为您的输入具有 Windows 样式的行尾 (CRLF)。您可以使用od -xc 查看回车。我继续在字符集中添加制表符以用作字段分隔符 (IFS),然后查看echo 之后是否有任何非空白字符。

    这是我测试的输入的十六进制转储(在“空”行中添加了空格和制表符):

    $ od -xc in.csv
    0000000    6954    656d    432c    6168    6e6e    6c65    4120    0a0d
              T   i   m   e   ,   C   h   a   n   n   e   l       A  \r  \n
    0000020    7328    2c29    5628    0d29    200a    2009    0d20    300a
              (   s   )   ,   (   V   )  \r  \n      \t          \r  \n   0
    0000040    302e    3030    3030    3030    2c30    2e30    3835    3530
              .   0   0   0   0   0   0   0   0   ,   0   .   5   8   0   5
    0000060    3538    3036    0a0d    2e30    3030    3636    3636    3736
              8   5   6   0  \r  \n   0   .   0   0   6   6   6   6   6   7
    0000100    302c    352e    3938    3331    3336    0d30    300a    302e
              ,   0   .   5   8   9   1   3   6   3   0  \r  \n   0   .   0
    0000120    3331    3333    3333    2c34    2e30    3835    3530    3538
              1   3   3   3   3   3   4   ,   0   .   5   8   0   5   8   5
    0000140    3036    0a0d    2e30    3230    3030    3030    3230    302c
              6   0  \r  \n   0   .   0   2   0   0   0   0   0   2   ,   0
    0000160    352e    3038    3835    3635    0d30    300a    302e    3632
              .   5   8   0   5   8   5   6   0  \r  \n   0   .   0   2   6
    0000200    3636    3636    2c39    2e30    3835    3139    3633    3033
              6   6   6   6   9   ,   0   .   5   8   9   1   3   6   3   0
    0000220    0a0d    2e30    3330    3333    3333    3633    302c    352e
             \r  \n   0   .   0   3   3   3   3   3   3   6   ,   0   .   5
    0000240    3938    3331    3336    0d30    300a    302e    3034    3030
              8   9   1   3   6   3   0  \r  \n   0   .   0   4   0   0   0
    0000260    3030    2c33    2e30    3835    3139    3633    3033    0a0d
              0   0   3   ,   0   .   5   8   9   1   3   6   3   0  \r  \n
    0000300
    

    【讨论】:

    • 这看起来既是一个很好的 bash 解决方案,也是一个很好的说明,为什么它值得转向更好的语言而不是把东西塞进 bash :)
    • 确实对我有用,我喜欢用于错误检查的脚本。我将把它作为一个单独的解决方案保存在我的目录中。谢谢!
    【解决方案3】:

    你可以用 perl 代替

    perl -lpe 'if($.==1){$x=<>;($T,$C)=$x=~/(\([^\)]\))/g;s/[^,]+\K,.*/ $T$& $C/;<>}' file
    
    Time (s),Channel A (V)
    0.00000000,0.58058560
    0.00666667,0.58913630
    0.01333334,0.58058560
    

    如果你有 CRLF 文件,看起来就像你一样。

    perl -lpe 'chop;if($.==1){$x=<>;($T,$C)=$x=~/(\([^\)]\))/g;s/[^,]+\K,.*/ $T$& $C/;<>}' file
    

    【讨论】:

    • 我不擅长 perl,所以我无法真正解析您的答案,但我得到的结果是第一行中的 (V) (s),Channel A。第三行已正确删除。
    • @Paul Probs 有回车,我会添加一个编辑。
    【解决方案4】:

    你可以这样做:

    awk 'BEGIN{
               FS=OFS=","
               }
         FNR==1{
               for(i=1;i<=NF;i++) l1[i]=$i
               }
         FNR==2{
               for (i=1;i<=NF;i++) l2[i]=$i
               }
         FNR==3{
               s=""
               for (i=1;i in l1 || i in l2; i++)
                   s=s ? s OFS l1[i] " " l2[i] : s l1[i] " " l2[i]
               print s    
               }
         FNR<=3{ next }
         1' file
    

    打印:

    Time (s),Channel A (V)
    0.00000000,0.58058560
    0.00666667,0.58913630
    0.01333334,0.58058560
    

    【讨论】:

    • 想想如果有那么多行,它可能更适合放在文件中而不是引号中。
    • 奇怪的是,这里的问题与@123 的答案(perl 方式)相同。第 1 行的输出是 (V) (s),Channel A。我的 csv 文件似乎有问题?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-08
    • 2016-07-18
    • 1970-01-01
    • 2021-12-27
    • 2019-05-28
    相关资源
    最近更新 更多