我有一张桌子
+-------+-------------+------+-----+---------+-------+
| 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 | |
+-------+-------------+------+-----+---------+-------+