#!/usr/bin/env perl
# -*- perl -*-
#
# $Id: show_db,v 2.8 2002/10/15 05:52:20 eserte Exp $
# Author: Slaven Rezic
#
# Copyright (c) 1997-2002 Slaven Rezic. All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
# Mail: <URL:mailto:
[email protected]>
# WWW: <URL:
http://www.rezic.de/eserte/>
#
=head1 NAME
show_db - take a quick look into dbm files
=head1 SYNOPSIS
show_db [-dbtype type] [-d delim] [-v] [-showtable]
[-key spec] [-val spec] [-color] [-sel key]
dbmfile
=head1 DESCRIPTION
C<show_db> shows the content of a dbm database file (like DB_File,
GDBM_File, or CDB_File). There is also some support for MLDBM
databases.
=head2 OPTIONS
=over
=item -dbtype type
The type of the database. This is usually the class name like
C<DB_File>. Normally, C<show_db> tries to determine itself, so you do
not have to specify this option.
Variants of the database type may be specified with a comma-separated
list. Currently valid variants are:
=over
=item DB_File,RECNO
The keys are the array indexes of the recno database.
=item MLDBM,I<DB>,I<Serializer>
where I<DB> is C<DB_File> or another dbm class and I<Serializer> is
C<Data::Dumper> or another serializer class.
=back
=item -v
Be verbose. Multiple C<-v> cause more verbosity.
=item -showtable
Pipe the output to C<showtable> from the C<Data::ShowTable>
distribution.
=item -color
Color the key values. Needs the C<Term::ANSIColor> module installed.
=item -key spec
=item -val spec
Treat the keys or values as special data structures:
=over
=item pack:I<packspec>
C<unpack> will be used on the data. See L<perlfunc/pack> for the
format of I<packspec>.
=item storable
The data will be handled as serialized by Storable.
=item freezethaw
The data will be handled as serialized by FreezeThaw.
=item perldata
The data will be handled as a perl value or reference.
=back
The C<-key> and C<-val> specifications may be overriden by C<-color>.
The values of MLDBM databases are handled according to the
I<serializer> variant.
=item -sel key
Select the value for the specified C<key> from the database. The
C<select> option may be given multiple times.
=back
=head1 README
show_db shows the content of a dbm database file (like DB_File,
GDBM_File, or CDB_File). There is also some support for MLDBM
databases.
=head1 PREREQUISITES
any dbm module
=head1 COREQUISITES
C<Data::ShowTable>, C<Term::ANSIColor>
=head1 OSNAMES
OS independent
=head1 SCRIPT CATEGORIES
Database
=head1 AUTHOR
Slaven Rezic <
[email protected]>
=head1 SEE ALSO
AnyDBM_File(3).
=cut
use strict;
use Fcntl;
use Getopt::Long;
use vars qw($VERSION);
$VERSION = sprintf("%d.%02d", q$Revision: 2.8 $ =~ /(\d+)\.(\d+)/);
my $delim = " => ";
my $v;
my $dbtype; # auto
my $do_showtable;
my $keyspec;
my $valspec;
my $cant_each;
my $do_color;
my @select;
if (!GetOptions(
'dbtype=s' => \$dbtype,
'd=s' => \$delim,
'v+' => \$v,
'showtable|table' => \$do_showtable,
'key=s' => \$keyspec,
'val=s' => \$valspec,
'color' => \$do_color,
'sel|select=s@' => \@select,
)) {
require Pod::Usage;
Pod::Usage::pod2usage(1);
}
my $file = shift || die "Specify db file";
my $db = defined $dbtype ? $dbtype : identify_db($file);
if (!defined $db) { die "Can't get DB type, please specify with -dbtype option" }
my $ref = open_db($file, $db);
my $keysub = sub { "<$_[0]>" };
my $valsub = sub { $_[0] };
if (defined $keyspec) {
$keysub = _spec_to_sub($keyspec);
}
if (defined $valspec) {
$valsub = _spec_to_sub($valspec);
}
if ($db =~ /^MLDBM/) { # XXX overrides -val
$valsub = _spec_to_sub("perldata");
}
if ($do_color) { # XXX overrides -key
require Term::ANSIColor;
$keysub = sub { Term::ANSIColor::color('red') . $_[0] . Term::ANSIColor::color('reset') };
}
my $pid;
if ($do_showtable) {
pipe(RDR, WTR);
$pid = fork;
if ($pid == 0) {
close WTR;
open(STDIN, "<&RDR") or die $!;
exec "showtable", "-d$delim";
die $@ if $@;
}
close RDR;
open(STDOUT, ">&WTR") or die $!;
}
my $selsub;
if (@select) {
foreach (@select) {
output_record($ref, $keysub, $valsub, $_);
}
} else {
output_db($ref, $keysub, $valsub, $selsub);
}
sub identify_db {
my $file = shift;
#XXX does not work with .dir/.pag files:
# if (!-e $file) {
# die "File $file does not exist";
# }
# if (!-r $file) {
# die "File $file is not readable";
# }
my @types = qw(DB_File GDBM_File NDBM_File SDBM_File
ODBM_File CDB_File DB_File,RECNO);
my $type;
TRY: {
foreach my $_type (@types) {
$type = $_type;
print STDERR "Try $type ... " if $v;
if ($type eq 'DB_File,RECNO' && eval "use $type; 1" &&
tie my @db, $type, $file, O_RDONLY, 0644, $DB_File::DB_RECNO) {
last TRY;
} elsif ($type eq 'CDB_File' && eval "use $type; 1" &&
tie my %db, $type, $file) {
last TRY;
} elsif (eval "use $type; 1" &&
tie my %db, $type, $file, O_RDONLY, 0644) {
last TRY;
}
if ($v > 1) {
warn "\$\@=$@, \$!=$!";
}
print STDERR "\n" if $v;
}
return undef;
}
print STDERR "OK!\n" if $v;
return $type;
}
sub open_db {
my($file, $type, %args) = @_;
if ($type eq 'DB_File,RECNO') {
require DB_File;
my @db;
tie @db, "DB_File", $file, O_RDONLY, 0644, $DB_File::DB_RECNO or
die "Can't type $file with $type: $!";
\@db;
} elsif ($type =~ /^MLDBM/) {
my(undef,$dbtype,$serializer) = split /,/, $type;
my @types = ($dbtype ne ""
? ($dbtype)
: (qw(DB_File GDBM_File NDBM_File ODBM_File CDB_File))
);
$MLDBM::Serializer = $serializer || "Data::Dumper";
require MLDBM;
my %db;
for $MLDBM::UseDB (@types) {
warn "Try $MLDBM::UseDB for MLDBM ... " if $v;
local $^W = 0; # XXX if !$v;
eval {
if ($MLDBM::UseDB eq 'CDB_File') {
tie %db, 'MLDBM', $file or
die "Can't tie $file with $type: $!";
} else {
tie %db, 'MLDBM', $file, O_RDONLY, 0644 or
die "Can't tie $file with $type: $!";
}
};
last if tied(%db);
}
if (!tied(%db)) {
warn $@;
}
\%db;
} elsif ($type =~ /^(BerkeleyDB),(.*)$/) {
($type, my $subtype) = ($1, $2);
eval "use $type"; die $@ if $@;
my %db;
tie %db, $type."::".$subtype, -Filename => $file or
die "Can't tie $file with ${type}::$subtype: $BerkeleyDB::Error";
\%db;
} else {
eval "use $type"; die $@ if $@;
my @tie_args = ($file);
if ($type ne 'CDB_File') {
push @tie_args, O_RDONLY, 0644;
}
my %db;
tie %db, $type, @tie_args or
die "Can't tie $file with @tie_args: $!";
\%db;
}
}
sub output_db {
my($dbref, $keysub, $valsub, $selsub) = @_;
if (ref $dbref eq 'ARRAY') {
my $i = 0;
foreach my $l (@$dbref) {
print $keysub->($i) . $delim . $valsub->($l) . "\n"
if !$selsub || $selsub->($i);
$i++;
}
} elsif (ref $dbref eq 'HASH') {
if ($cant_each) {
foreach my $key (keys %$dbref) {
my $val = $dbref->{$key};
print $keysub->($key) . $delim . $valsub->($val) . "\n"
if !$selsub || $selsub->($key);
}
} else {
while(my($key,$val) = each %$dbref) {
print $keysub->($key) . $delim . $valsub->($val) . "\n"
if !$selsub || $selsub->($key);
}
}
}
}
sub output_record {
my($dbref, $keysub, $valsub, $key) = @_;
if (ref $dbref eq 'ARRAY') {
print $keysub->($key) . $delim . $valsub->($dbref->[$key]) . "\n";
} elsif (ref $dbref eq 'HASH') {
print $keysub->($key) . $delim . $valsub->($dbref->{$key}) . "\n";
}
}
sub _spec_to_sub {
my($spec) = @_;
require Data::Dumper;
my $dd = sub {
my $out = Data::Dumper->new([$_[0]],[])->Useqq(1)->Indent(0)->Dump;
$out =~ s/\$VAR1\s*=\s*//;
$out;
};
if ($spec =~ /^pack:(.*)/) {
my $pack = $1;
return sub { unpack($pack, $_[0]) };
} elsif ($spec =~ /^storable$/i) {
require Storable;
#XXX? $cant_each = 1;
return sub { $dd->(Storable::thaw($_[0])) };
} elsif ($spec =~ /^freezethaw$/i) {
require FreezeThaw;
#XXX? $cant_each = 1;
return sub { $dd->(FreezeThaw::thaw($_[0])) }; # XXX check
} elsif ($spec =~ /^perldata$/i) {
#XXX? $cant_each = 1;
return sub { ref $_[0] ? $dd->($_[0]) : $_[0] };
} else {
die "Can't parse specification <$spec>";
}
}