将表格转换为 csv 并返回

将表格转换为 csv 并返回

我有一张桌子

+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   | PRI | NULL    |       |
| foo   | varchar(10) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+

我想定义两个脚本,一个可以将此类表转换为 csv,另一个可以将其转换回来。

我知道 sed 有这种可能性

sed -e '1d;3d;$d' -e 's/^|//' -e 's/|$//' -e 's/|/,/g' file

但这是不可靠的。
可靠的方法是|在第二行(第一行和最后一行除外)上找到字符的位置,然后在每行上将该位置中的字符转换为字符,并删除它们周围的空格。它可能可以用 awk 完成,但我不知道如何。

答案1

首先,使用管道字符来找出字段的开始和结束位置是错误的 - 您已经有一行精确地定义了它们,而字段内容没有机会包含与字段分隔符相同的字符:第一行,其中包含仅有的列标记 ( +) 和填充字符 ( -)。

这是一个 perl 脚本 ( table-to-csv.pl),用于提取数据并将其打印为 csv 格式的文件。假设输入数据是一个 sql 表定义,它会引用每个数据字段 - 更通用的版本可能会尝试确定是否需要引用(即该字段是否为数字)。

这个脚本比实际需要的要复杂一些,例如,没有真正需要构建@headers@data数组,一旦知道列长度,就可以在读入时提取并打印每一行。如果需要,这种方式可以更轻松地对标头和数据之一或两者执行进一步处理。

#!/usr/bin/perl -w

use strict;

my @columns = ();
my @headers = ();
my @data = ();

sub extract;  # forward declaration of extract subroutine

# main loop
while(<>) {
  chomp;
  next if (m/^\s*$/);

  if(/^\+-/) {
    # use the '+' chars in the first line to find column positions
    next if (@columns != 0);
    my $i=0;
    while ($i >= 0 && $i < length($_)) {
      my $e=index($_,"+",$i+1);
      # store starting pos & length pair for each column
      push @columns, [ $i+2, $e-3-$i ];
      $i=$e;
    };
    pop @columns;  # last pair will always be bogus, dump it.
  } else {         # extract the headers and data
    if (!@headers) {
      @headers = extract($_,@columns);       # array of field header names
    } else {
      push @data, [ extract($_,@columns) ];  # array of arrays of field data
    };
  };
};

# output in simple csv format.
print join(',',@headers), "\n";

foreach my $l (@data) {
  print join(',',@{ $l }), "\n";
};

### subroutines
sub extract {
  my ($line,@cols) = @_;
  my @f=();

  foreach my $c (@cols) {
    my $d = substr($line,$c->[0],$c->[1]);
    $d =~ s/^\s*|\s*$//g; # strip leading & trailing spaces
    push @f, '"' . $d .'"' ;
  }
  return @f;
};

输出:(将您的输入表保存为 table.txt)

$ ./table-to-csv.pl table.txt 
"Field","Type","Null","Key","Default","Extra"
"id","int(11)","NO","PRI","NULL",""
"foo","varchar(10)","YES","","NULL",""

您问题的第二部分确实需要构建数组的稍微复杂性@data。读取和解析 CSV 很容易,特别是如果您使用像 perlText::CSV模块这样的库来处理带引号和不带引号的字段...但是为了获得正确的输出格式,您需要两次传递数据。第一个查找并存储每个字段的最大宽度(用于控制输出格式),第二个打印数据。

以下 perl 脚本 ( csv-to-table.pl) 需要该Text::CSV模块。在 debian 等系统上,它位于libtext-csv-perl软件包中。其他发行版将具有类似的包名称。或者您可以使用 自行安装cpan

#!/usr/bin/perl -w

use strict;
use Text::CSV;

my @data;
my @lengths;

my $csv = Text::CSV->new ();

while (my $row = $csv->getline(*ARGV)) {
  my @fields = @$row;
  foreach my $i (0..@fields-1) {   # find the largest width for each field
    my $len = length($fields[$i]);
    $lengths[$i] = $len if (!defined($lengths[$i]) || $lengths[$i] <= $len);
  };
  push @data, [ @fields ];  # stuff each record into an array of arrays
};

my $hdr='+';
my $fmt='';

foreach (@lengths) {
  # build the header/separator line and the printf format string
  $hdr .= '-' x ($_+2) . '+';
  $fmt .= '| %-' . ($_) . 's ' ;
};
$fmt .= "|\n";
$hdr .= "\n";

# output the table

print $hdr;
printf "$fmt", @{ $data[0] };
print $hdr;

foreach my $i (1..@data-1) {
  printf $fmt, @{ $data[$i++] };
}

print $hdr;

输出:

$ ./table-to-csv.pl table.txt  | ./csv-to-table.pl
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   | PRI | NULL    |       |
| foo   | varchar(10) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+

相关内容