用awk清理csv文件的标题

时间:2017-06-03 23:22:31

标签: linux bash shell csv awk

我有一堆看起来像这样的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行),但是切掉第二行参数并将它们粘贴在第一行字段后面超出了我的能力;(

请帮帮我! 动机:这些文件很多。手工完成是不可行的......

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

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

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

如果有帮助,I uploaded a sample infile

4 个答案:

答案 0 :(得分: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 "$@"

给出一个文件&#39;&#39;使用您的示例输入

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之后留下了任何非空白字符。

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

$ 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

答案 1 :(得分:1)

您可以使用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

答案 2 :(得分: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'

答案 3 :(得分:0)

你可以这样做:

Either

打印:

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