私にテーブルがあります。
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| foo | varchar(10) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2つのスクリプトを定義したいと思います。 1つはそのテーブルをcsvに変換でき、もう1つは再変換できます。
私はsedにこの可能性があることを知っています
sed -e '1d;3d;$d' -e 's/^|//' -e 's/|$//' -e 's/|/,/g' file
しかし、これは信頼できません。
信頼できる方法は、|
2行目(最初の行と最後の行を除く)で文字位置を見つけて、各行でその位置の文字を文字に変換して,
その周囲のスペースを削除することです。おそらくawkで行うことができますが、どうすればいいかわかりません。
答え1
まず、パイプ文字を使用してフィールドの始まりと終わりが間違った場所を見つけます。すでにこれを正確に定義する行があり、フィールドの内容にフィールド区切り文字と同じ文字が含まれる可能性はありません。ただ列マーカー(+
)と塗りつぶし文字(-
)。
table-to-csv.pl
データを抽出してcsv形式のファイルに印刷するPerlスクリプト()。入力データが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",""
質問の2番目の部分には、配列の構築に若干の複雑さが必要です@data
。 CSVを読んで解析するのは簡単です。特に、Text::CSV
Perlモジュールなどのライブラリを使用して引用符付きフィールドと引用符付きフィールドを処理する場合...しかし、正しい出力形式を取得するにはデータを2回渡す必要があります。 1つ目は各フィールドの最大幅(出力形式を制御するために使用されます)を見つけて保存し、2つ目はデータを印刷します。
次の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 | |
+-------+-------------+------+-----+---------+-------+