【问题标题】:How to write each row of a text file into a CSV row如何将文本文件的每一行写入 CSV 行
【发布时间】:2014-02-16 02:58:23
【问题描述】:

我是 perl 新手,我正在尝试获取格式如下的文件(实际上是 .idx 文件)

 Monday       Foo Name             43212    
 Tuesday      Name Foo Foo         43252
 Tuesday      Name                 50322 
 Wednesday    Foo Name             53221
 Thursday     Foo Bar Foo Name     24353

我想将其输出为 csv 文件。该文件应与此完全相同,但 CSV 格式除外,以便 Excel 可以读取它。另外,我只希望包含周二的行,以便 CSV 文件看起来像

 Tuesday      Name Foo Foo     43252
 Tuesday      Name             50322

在 Excel 中。我还有几个idx 格式为formYYYY_Q.idx 的文件,其中YYYY 指的是年份,Q 指的是季度。我想遍历我拥有的所有 .idx 文件并创建一个大型 CSV 文件,每个 .idx 文件中只有行,开头为 Tuesday。我到目前为止的代码是

 #!/usr/bin/perl

 use warnings;
 use strict;
 use Text::CSV;

 my $csvfile= Text::CSV->new({binary=>1,auto_diag=>1});
 $csvfile->column_names("Day","Name","Number");

 my @datalines;
 my $idxfile="form1994_1.idx";

 open(INFILE, "< $idxfile") or die $!;
 open(my $outfile, "> Master.csv") or die $!;

 while(<INFILE>){

      if(/^Tuesday/){

            chomp($_);
            push(@nsarlines, $_);

     }

 }

当我将 open 命令替换为 open(OUTFILE, "&gt; Master.txt") or die $!; 并将其包含在 while 循环外的最后一行代码中时:

 print OUTFILE map {"$_ \n"} @nsarlines;

Master.txt 文件看起来像我想要的那样

 Tuesday      Name Foo Foo     43252
 Tuesday      Name             50322

但是,如果我使用上面编写的 open 命令,请在 while 循环之外执行类似的操作:

 $csvfile-> print($outfile, \@test);

我得到一个包含整个 $_ 字符串的 CSV 文件,这是 Master.csv 文件的每个单元格中 .idx 文件的一行。我很难弄清楚如何使 perl 使每个 .idx 行成为 CSV 行,而无需手动将逗号插入 $_(不是一个优雅或理想的选择)。

我需要做的第二件事是我有文件formYYY_Q.idx 都在同一个目录中,我想自动浏览每个文件,提取以星期二开头的行并将它们添加到 Master.csv 文件(或者更确切地说,完成所有这些并在最后编写一次 Master.csv 文件)。我认为File::Find 可能能够做到这一点,但我一直无法弄清楚如何使用它。

感谢您的帮助。

【问题讨论】:

  • 为什么不想插入逗号? csv 是逗号分隔值的首字母缩写词。您可以使用split /\s+/join "," 轻松完成。老实说,我没有理由使用模块来编写 csv 文件。读书,是的。写作,没有。
  • @avitevet 实际文件具有不同字长的Fooname 变量。一个可能是Company Foofirm,而另一个可能是Holding Foofirm Parters。如果我使用split /\s+/,它将在我的数据中有空格的任何地方放置一个逗号。所以我会得到"Tuesday,Holding,Foofirm,Partners,43252" 而不是"Tuesday,Holding Foofirm Partners,43252"。而且公司名称由多少个单词组成,也没有任何固定的统一模式。
  • 如果您不想处理您的行,而只是重新打印它们,您也许应该使用 grep:grep "^Tuesday" form1994_1.idx
  • 我明白了...但是必须有一些分隔符,对吗?否则,任何脚本如何知道正确放置逗号的位置?你能在分隔符上拆分吗?我同意,对于 TLP,如果您只想重新打印它们,您可以使用单个命令行来执行此操作: find 。 -name '*.idx' -type f -exec grep Tuesday '{}' \; > Master.csv
  • @avitevet 不幸的是,除了列之间“有点”一致的空格之外,没有任何分隔符。我希望隐藏的字符只是单独的\t,但它们都是不同长度的\s。我最终只是“手动”在循环的每次迭代中插入逗号,并且不得不修补在某些列中添加空格,以便所有 64,000 行都正确显示。感谢您的意见。

标签: perl csv


【解决方案1】:

有两种方法可以使用 File::Find。一种是您使用wanted 函数将有关文件的数据添加到全局列表/队列/变量,然后在find 调用之后处理数据。另一种方法是在wanted 函数中执行所有处理。

我个人不喜欢使用全局变量将数据传递到函数之外,但不幸的是 File::Find 是您的选择。这是他们这样做的一个例子:http://www.perlmonks.org/?node_id=217378。在示例中,他们使用 %size 散列将数据传递出匿名 wanted 函数。这可能是您最好的选择,您可以将匹配的文件名添加到全局列表中,然后遍历列表,将每个文件中的数据写入您的 CSV。

另一个选项是在wanted 函数中执行您的处理。但这又不是最理想的,因为您需要使用全局变量将有关打开的 CSV 文件的信息传入wanted 函数。

另一个选项,假设您的所有 .idx 文件可以保证在同一个目录中(而不是在同一个目录树中)是使用 opendir 和 readdir 函数。 http://perldoc.perl.org/functions/readdir.html

my $dh;   # directory handle
opendir($dh, $your_dir) || die $!;
my @idxfiles = grep {/\.idx$/} readdir($dh);
closedir($dh);

foreach my $idxfile (@idxfiles) {
   open(INFILE, "< $idxfile") or die $!;
   ... the rest of your code ...
}

【讨论】:

    【解决方案2】:

    结合了一些 op 的代码和一些 avitevet 的代码,得出了这个:

    #!/usr/bin/perl
    use warnings;
    use strict;
    
    opendir(DIR, ".") or die $!;
    my @idxfiles = sort(grep {/\.idx$/} readdir(DIR));
    closedir(DIR);
    
    open(OUT, "> Master.csv") or die $!;
    foreach my $idx (@idxfiles) {
      open(F, "$idx") or die $!;
      while (<F>) {
        if (m/^Tuesday/) {
          my @fields = split(/\s+/);
          my $day = shift(@fields); # grab the first one
          my $zip = pop(@fields); # grab the last one;
          my $middle = join(" ", @fields); # merge the rest back together
          print OUT "$day,$middle,$zip\n";
        }
      }
      close(F)
    }
    close(OUT);
    

    【讨论】:

    • 感谢您的代码。我的实际文件有超过三列。是否可以使用shift 遍历所有列,直到最后一列,然后在最后加入它们?
    • 在我的代码中,该行按空格/空格分隔。由于您已指出第二个字段也可以包含空格,因此我的代码仅在您移开第一列然后弹出最后一列时才有效。如果您一直在列中移动,那么您将在其中有空格的地方分解第二个字段。如果这是唯一带空格的字段,您可以关闭第一个字段,然后弹出第 3 列及以后的列,留下上面显示的“$middle”。
    【解决方案3】:

    我赞赏您对问题的清晰描述和您尝试的解决方案!

    鉴于您的叙述,包括将所有 idx 文件放在一个目录中,请考虑以下解决方案——在包含 *.idx 的目录中执行:

    use strict;
    use warnings;
    
    open my $outfile, '>', 'Master.csv' or die $!;
    print $outfile "Day,Name,Number\n";
    
    for my $idxfile (<*.idx>) {
        next unless $idxfile =~ /^form\d{4}_\d\.idx/;
        open my $infile, '<', $idxfile or die $!;
    
        while (<$infile>) {
            if (/^Tuesday/) {
                my ( $day, $name, $num ) = /(\w+)\s+(.+?)\s+(\d+)/;
                print $outfile "$day,$name,$num\n";
            }
        }
    
        close $infile;
    }
    
    close $outfile;
    

    标头首先写入 Master.csv 文件。 &lt;*.idx&gt; 构造是一个 glob,它从当前目录生成 *.idx 文件的列表。接下来,使用正则表达式来确保文件名与您的命名规范匹配。如果目录中只有你要处理的文件,你可以删除这一行。

    当前 idx 文件已打开并处理。在您的代码中,正则表达式用于检查行首的“星期二”。如果遇到这样的行,正则表达式会捕获三个字段:

    /(\w+)\s+(.+?)\s+(\d+)/
       ^   ^  ^    ^   ^
       |   |  |    |   |
       |   |  |    |   + - One+ digits - Number
       |   |  |    + - One+ whitespaces
       |   |  + - One+ any characters (except newline) - Name
       |   + - One+ whitespaces 
       + - One+ 'word' characters - Day
    

    这些捕获的字段(以逗号分隔)被写入 Master.csv 文件。当当前 idx 文件被完全读取时,它被关闭,并且下一个 idx 文件被处理——如果有的话。最后,关闭 Master.csv 文件。

    希望这会有所帮助!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-06
      相关资源
      最近更新 更多