Text-CSV_XS
view release on metacpan or search on metacpan
examples/csvdiff view on Meta::CPAN
#!/pro/bin/perl
use 5.012000;
use warnings;
# csvdiff: Show differences between CSV files
# (m)'23 [05 Aug 2023] Copyright H.M.Brand 2009-2025
our $VERSION = "1.03 - 20230805";
sub usage {
my $err = shift and select STDERR;
print "usage: csvdiff [--no-color] [--html] [-w|-b|-Z] file.csv file.csv\n",
" provides colorized diff on sorted CSV files\n",
" assuming first line is header and first field is the key\n",
" --no-color do not use colors\n",
" -h --html produce HTML output\n",
" -w --ignore-all-space ignore all white space\n",
" -b --ignore-space-change ignore changes in the amount of white space\n",
" -Z --ignore-trailing-space ignore white space at line end\n",
" -o F --output=F send output to file F\n";
exit $err;
} # usage
use Getopt::Long qw(:config bundling nopermute );
my $opt_c = !$ENV{NO_COLOR};
GetOptions (
"help|?" => sub { usage (0); },
"V|version" => sub { print "csvdiff [$VERSION]\n"; exit 0 },
"w|ignore-all-space!" => \my $opt_w,
"b|ignore-ws|ignore-space-change!" => \my $opt_b,
"Z|ignore-trailing-space!" => \my $opt_Z,
"c|color|colour!" => \ $opt_c,
"h|html" => \my $opt_h,
"o|output=s" => \my $opt_o,
) or usage (1);
@ARGV == 2 or usage (1);
if ($opt_o) {
open STDOUT, ">", $opt_o or die "$opt_o: $!\n";
}
use HTML::Entities;
use Term::ANSIColor qw(:constants);
use Text::CSV_XS;
my $csv = Text::CSV_XS->new ({ binary => 1, auto_diag => 0 });
if ($opt_h) {
binmode STDOUT, ":encoding(utf-8)";
my $name = $^O eq "MSWin32" ? Win32::LoginName () : scalar getpwuid $<;
print <<"EOH";
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>CFI School updates</title>
<meta name="Generator" content="perl $]" />
<meta name="Author" content="$name" />
<meta name="Description" content="CSV diff @ARGV" />
<style type="text/css">
.rd { background: #ffe0e0; }
.gr { background: #e0ffe0; }
.hd { background: #e0e0ff; }
.b0 { background: #e0e0e0; }
.b1 { background: #f0f0f0; }
.r { color: red; }
.g { color: green; }
</style>
</head>
<body>
<h1>CSV diff @ARGV</h1>
<table>
EOH
$::{RED} = sub { "\cA\rr"; };
$::{GREEN} = sub { "\cA\rg"; };
$::{RESET} = sub { ""; };
}
elsif (!$opt_c) {
$::{$_} = sub { "" } for qw( RED GREEN RESET );
}
my @f;
my $opt_n = 1;
foreach my $x (0, 1) {
open my $fh, "<", $ARGV[$x] or die "$ARGV[$x]: $!\n";
my $n = 0;
while (1) {
my $row = $csv->getline ($fh) or last;
@$row and push @{$f[$x]}, $row;
$n++ && $row->[0] =~ m/\D/ and $opt_n = 0;
}
}
my @n = map { $#{$f[$_]} } 0, 1;
my @i = (1, 1);
my $hdr = "# csvdiff < $ARGV[0] > $ARGV[1]\n";
$f[$_][1+$n[$_]][0] = $opt_n ? 0x7FFFFFFF : "\xff\xff\xff\xff" for 0, 1;
my %cls;
%cls = (
"b" => 0,
"-" => sub { "rd" },
"+" => sub { "gr" },
"H" => sub { "hd" },
"<" => sub { $cls{b} ^= 1; "b$cls{b}" },
">" => sub { "b$cls{b}" },
);
( run in 1.105 second using v1.01-cache-2.11-cpan-39bf76dae61 )