从CSV文件中创建多个CSV文件

7

操作系统:OSX或Linux

我正在尝试自动化我的工作流程,每周我都会收到一个Excel文件,然后将其转换为CSV格式。

例如:

,,L1,,,L2,,,L3,,,L4,,,L5,,,L6,,,L7,,,L8,,,L9,,,L10,,,L11,
Title,r/t,needed,actual,Inst,needed,actual,Inst,needed,actual,Inst,needed,actual,Inst,neede d,actual,Inst,needed,actual,Inst,needed,actual,Inst,needed,actual,Inst,needed,actual,Inst,needed,actual,Inst,needed,actual,Inst
EXAMPLEfoo,60,6,6,6,0,0,0,0,0,0,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
EXAMPLEbar,30,6,6,12,6,7,14,6,6,12,6,6,12,6,8,16,6,7,14,6,7.5,15,6,6,12,6,8,16,6,0,0,6,7,14
EXAMPLE1,60,3,3,3,3,5,5,3,4,4,3,3,3,3,6,6,3,4,4,3,3,3,3,4,4,3,8,8,3,0,0,3,4,4
EXAMPLE2,120,6,6,3,0,0,0,6,8,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
EXAMPLE3,60,6,6,6,6,8,8,6,6,6,6,6,6,0,0,0,0,0,0,6,8,8,6,6,6,0,0,0,0,0,0,0,10,10
EXAMPLE4,30,6,6,12,6,7,14,6,6,12,6,6,12,3,5.5,11,6,7.5,15,6,6,12,6,0,0,6,9,18,6,0,0,6,6.5,13

以下是Excel中的截图:alt text

我需要做的是为第一行的每个实例创建多个csv文件,例如L1、L2、L3、L4...

在每个csv文件中,它需要包含标题、r/t、所需信息。

因此,对于L1,示例输出将如下:

EXAMPLEfoo,60,6
EXAMPLEbar,30,6
EXAMPLE1,60,3
EXAMPLE2,120,6
EXAMPLE3,60,6
EXAMPLE4,30,6

对于L2:
EXAMPLEfoo,60,0
EXAMPLEbar,30,6
EXAMPLE1,60,3
EXAMPLE2,120,0
EXAMPLE3,60,6
EXAMPLE4,30,6

等等再说。

我尝试使用sed和awk并搜索了谷歌,但没有找到真正解决问题的方法。

我想Perl可能特别适合这个任务,或者也许是Python,所以我很乐意接受用户的建议。

那么,有什么建议吗?

提前致谢。


你收到的 Excel 文件是哪种格式 -- 2003、2007,还是其他? - FMc
2007年在Mac上,我曾尝试使用Automator和Excel钩子来实现这一点,但都没有成功。理想情况下,我希望能够从bash脚本中运行它,这也解释了我对sed和awk的尝试。 - S1syphus
1
请查看我的FOSS项目http://code.google.com/p/csvfix,这是一个用于执行此类操作的工具。 - anon
鉴于不同的L覆盖多列,您如何确定要选择哪个? - HerbN
你好,第一行的条目数量是恒定的吗?L1 是唯一错过 Inst 列的吗? - user314463
抱歉,回复晚了。不,它们不是常量。 - S1syphus
6个回答

3

Perl "一行代码"

perl -MText::CSV_XS -e'$c=Text::CSV_XS->new({binary=>1,eol=>"\n"});%a=map{$i++;/^L\d+$/?($_=>$i):()}@{$c->getline(*ARGV)};open$b{$_},">$_"for keys%a;while($f=$c->getline(*ARGV)){$c->print($b{$_},[@$f[0,1,$a{$_}]])for keys%a}'

如果有阅读困难的人:

$ echo '$c=Te...' | perltidy
$c = Text::CSV_XS->new( { binary => 1, eol => "\n" } );
%a = map { $i++; /^L\d+$/ ? ( $_ => $i ) : () } @{ $c->getline(*ARGV) };
open $b{$_}, ">$_" for keys %a;
while ( $f = $c->getline(*ARGV) ) {
    $c->print( $b{$_}, [ @$f[ 0, 1, $a{$_} ] ] )
      for keys %a;
}

2

仅使用 AWK:

awk -F, -vOFS=, -vc=1 '
    NR == 1 {
        for (i=1; i<NF; i++) {
            if ($i != "") {
                g[c]=i;
                f[c++]=$i
            }
        }
    }
    NR>2 {
        for (i=1; i < c; i++) {
            print $1,$2, $g[i] > "output_"f[i]".csv"
        }
    }' data.csv

作为一行代码:

awk -F, -vOFS=, -vc=1 'NR == 1 {for (i=1; i<NF; i++) {if ($i != "") {g[c]=i; f[c++]=$i}}} NR>2 { for (i=1; i < c; i++) {print $1,$2, $g[i] > "file_"f[i]".csv" }}' data.csv

示例输出:

$ cat file_L1.csv
EXAMPLEfoo,60,6
EXAMPLEbar,30,6
EXAMPLE1,60,3
EXAMPLE2,120,6
EXAMPLE3,60,6
EXAMPLE4,30,6
$ cat file_L2.csv
EXAMPLEfoo,60,0
EXAMPLEbar,30,6
EXAMPLE1,60,3
EXAMPLE2,120,0
EXAMPLE3,60,6
EXAMPLE4,30,6
$ cat file_L11.csv
EXAMPLEfoo,60,0
EXAMPLEbar,30,6
EXAMPLE1,60,3
EXAMPLE2,120,0
EXAMPLE3,60,0
EXAMPLE4,30,6

我没有得到OP所述的输出。 - ghostdog74
NR == 1 循环构建了一个包含 L1 等单元格位置和内容的数组,而 NR>2 循环则水平移动每个记录,将正确的数据输出到正确的文件中。在我的系统上,> 似乎是追加的,也许重定向应该改为 >>。你的脚本会整个文件循环11次,而我的只需要一次。 - Dennis Williamson
谢谢回复,我希望最终能够联系到每个人,但丹尼斯,你的看起来最有前途,所以我会从这里开始。您在哪个版本的awk上运行了此命令?$ awk -F,-vOFS =,-vc = 1'NR == 1 {for(i = 1; i <NF; i ++){if($ i!=“”){g [c] = i; f [c ++] = $ i}}} NR> 2 {for(i = 1; i <c; i ++){print $ 1,$ 2,$ g [i]>“file_”f [i]“.csv”}}}'data.csv awk:无效的-v选项我将尝试升级默认的OSx安装,其中包含awk版本20070501,我会发布我的结果。 - S1syphus
还是不行,可能是因为我正在使用贝尔实验室的源代码,我会编译 GNU 并回来。 - S1syphus
@S1syphus:你说你正在“使用贝尔实验室的源代码”,但你也说“默认的OSx安装”,这似乎是矛盾的。如果你用/full/path/to/official/osx/awk ...运行它会发生什么?(另外,type -a {,g,n}awk显示了什么?) - Dennis Williamson
显示剩余7条评论

2
use strict;
use warnings;

use Text::CSV;
my $csv = Text::CSV->new;

sub parse_line {
    $csv->parse(shift) or die $!;
    return $csv->fields;
}

my @metadata;
my @files  = parse_line(scalar <>);
my @header = parse_line(scalar <>); # Ignore.
for my $i (0 .. $#files){
    next unless length $files[$i];
    open(my $h, '>', "$files[$i].csv") or die $!;
    push @metadata, {column => $i, handle => $h};
}

while (my $line = <>){
    my @fields = parse_line($line);
    for my $m (@metadata){
        $csv->print($m->{handle}, [ @fields[0, 1, $m->{column}] ]);
        print {$m->{handle}} "\n";
    }
}

1

试一下这个

#!/bin/bash
awk 'BEGIN{ OFS=FS="," }
NR==1{
 for(i=1;i<=NF;i++){
   if($i){ f[i]=$i }
 }
}
NR>2{ for(o in f){ print $1,$2, $o > "file_"f[o]".csv" } } ' file

输出

$ cat file_L1.csv
EXAMPLEfoo,60,6
EXAMPLEbar,30,6
EXAMPLE1,60,3
EXAMPLE2,120,6
EXAMPLE3,60,6
EXAMPLE4,30,6

$ cat file_L2.csv
EXAMPLEfoo,60,0
EXAMPLEbar,30,6
EXAMPLE1,60,3
EXAMPLE2,120,0
EXAMPLE3,60,6
EXAMPLE4,30,6

谢谢您的回复,我认为这可能只是我的问题,因为像这样做的所有示例(包括您的示例)都会给我一个类似于awk:syntax error at source line 7的错误。 上下文是 NR>2{ for(o in f){ print $1,$2, $o > >>> "file_"f <<< [o]".csv" } } - S1syphus
我不确定为什么。您可以尝试使用dos2unix(或unix2dos)将脚本的换行符转换为适用于OSX的格式。 - ghostdog74

1

请查看perl模块Text::CSV_XS——逗号分隔值操作例程。我发现在处理CSV文件时,这个模块非常有帮助。


0
在Python中,有点狡猾和未经测试,但应该可以完成工作:
import csv
r = csv.reader(open(r'file.csv'), dialect='excel')
topline = r.next()
headerline = r.next()

lastcell = ''
for i, cell in enumerate(topline): #Copy cells forwards in the top line, so L1 for example goes across all cells
    if cell == '':
        topline[i] = lastcell
    else:
        lastcell = cell

for i in range(len(headerline)): #Copy the topline cells into the header line, so the headerline cells should be unique
    headerline[i] = '-'.join((topline[i], headerline[i]))

rows = [dict(zip(headerline, line)) for line in r]

# Rows should now consist of dicts of the form {'Title': 'EXAMPLEfoo', 'r/t': '60', 'L1-needed': '6' ...}

for lval in frozenset(topline): #Use frozenset to ensure we only have unique values.
    if lval != '': #Make sure we don't look at the blank value
        w = csv.writer(open(r'%s.csv' % lval, 'w'), dialect='excel')
        for row in rows:
            line = [row['Title'], row['r/t'], row['-'.join((lval, 'needed'))]]
            w.writerow(line)

你的“for cell in topline:”循环实际上没有做任何事情;将值赋给“cell”并不会改变“topline”的元素。你需要用for i, cell in enumerate(topline): if cell == '': topline[i] = lastcell替换它,以此类推。而且为什么在最后一个循环中使用frozenset,而不是普通的set? - Peter Milley
@Peter:好眼力;我总是忘记 Python 列表枚举中的那个漏洞。我马上更新。我使用 frozenset,因为当时它似乎是个好主意……据我所知,在这种情况下没有任何区别。 - me_and

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接