#!/usr/bin/perl -w # # Test program for the dump_data() subroutine. # # 061124 liverpole -- created # 061128 liverpole -- updated to incorporate graff's suggestions ############################################################################### # Strict use strict; use warnings; # Libraries use File::Basename; use FileHandle; use Data::Dumper; use Getopt::Long; # Globals my $iam = basename $0; my $syntax = " syntax: $iam [switches] Displays a binary dump of given , or of the contents of STDOUT if '-' is given instead of the filename. Switches: start ... Starting offset (default = 0) end ... Ending offset (default = 1 (eof)) left ... Left separator char (default = '|') mid ... Middle separator char (default = '|') right ... Right separator char (default = '|') bin ... Non-printing binary char (default = '.') afmt ... Address printf format (default '%08x') bfmt ... Bytes printf format (default '%02x') max ... Max bytes to display (default = -1 (entire file)) count ... Total bytes perl line (default = 16) Examples: Show bytes 100 through 200 of STDOUT: % perl dumptest.pl -start 100 -end 200 - Show all bytes of file 'binary.dat' % perl dumptest.pl binary.dat "; # Command-line my $popts = { }; my $result = GetOptions ( "start=s" => \$popts->{'start'}, "end=s" => \$popts->{'end'}, "left=s" => \$popts->{'left'}, "mid=s" => \$popts->{'mid'}, "right=s" => \$popts->{'right'}, "bin=s" => \$popts->{'bin'}, "afmt=s" => \$popts->{'afmt'}, "bfmt=s" => \$popts->{'bfmt'}, "max=s" => \$popts->{'max'}, "count=s" => \$popts->{'count'}, ); map { defined $popts->{$_} or delete $popts->{$_} } (keys %$popts); # Main program (my $fname = shift) or die $syntax; my $fh = dump_data($fname, %$popts); ############################################################################### # dump_data() # # Given a filename or filehandle $1, and an optional hash of arguments and # values, displays a data dump of the given file. If '-' is specified for # the filename (or the filename is undefined), STDIN is used instead. # # The following optional arguments modify the behavior: # # Key => value Description Default (meaning) # --------------------------------------------------------------------------- # start => offset Starting file offset 0 # end => offset Ending file offset -1 ("end-of-file")) # left => string Left separator char '|' # mid => string Middle separator char '|' # right => string Right separator char '|' # bin => string Non-printing binary char '.' # afmt => string Address printf format '%08x' # bfmt => string Bytes printf format '%02x' # max => number Max bytes to display -1 (entire file) # count => number Total bytes per line 16 # out => number Scalar ref to store output 0 (print to STDOUT) # # For example, the following illustrates a dump of the first 128 bytes # of a .JPG file with no optional arguments: # # 00000000|ff d8 ff e0 00 10 4a 46|49 46 00 01 01 01 00 48|......JFIF.....H # 00000010|00 48 00 00 ff e1 25 88|45 78 69 66 00 00 49 49|.H....%.Exif..II # 00000020|2a 00 08 00 00 00 09 00|0f 01 02 00 06 00 00 00|*............... # 00000030|7a 00 00 00 10 01 02 00|16 00 00 00 80 00 00 00|z............... # 00000040|12 01 03 00 01 00 00 00|01 00 00 00 1a 01 05 00|................ # 00000050|01 00 00 00 96 00 00 00|1b 01 05 00 01 00 00 00|................ # 00000060|9e 00 00 00 28 01 03 00|01 00 00 00 02 00 00 00|....(........... # 00000070|32 01 02 00 14 00 00 00|a6 00 00 00 13 02 03 00|2............... # # The output format is as follows: file offset (address) on the left, then # the left separator char "|" followed by 16 bytes of data (with the middle # separator char "|" splitting the data to make it more readable), followed # by the right separator char "|" and the ascii representation of each byte # (with "." for non-printing chars). ############################################################################### sub dump_data { my ($fh, %opts) = @_; # Parse options, and assign defaults my $passign = sub { my ($key, $default) = @_; defined($opts{$key})? $opts{$key}: $default }; my $start = $passign->('start', 0); # Starting file offset my $end = $passign->('end', -1); # Ending file offset my $left = $passign->('left', "|"); # Left separator char my $mid = $passign->('mid', "|"); # Middle separator char my $right = $passign->('right', "|"); # Right separator char my $bin = $passign->('bin', "."); # Non-printing binary char my $afmt = $passign->('afmt', "%08x"); # Address printf format my $bfmt = $passign->('bfmt', "%02x"); # Bytes printf format my $max = $passign->('max', -1); # Max bytes to display my $count = $passign->('count', 16); # Total bytes per line my $pout = $passign->('out', 0); # Scalar ref to store output # If a file (or STDIN), open it my $b_stdin = (!($fh || 0) or $fh eq '-'); if ($b_stdin) { # If $fh is '-', use STDIN $fh = \*STDIN; binmode $fh; } elsif (ref $fh eq "") { # If $fh is a filename, open it my $fname = $fh; (-e $fname) or die "$iam: no such file '$fname'\n"; $fh = new FileHandle; sysopen($fh, $fname, 0) or die "$iam: can't read '$fname' ($!)\n"; } # Lexically-scoped data my ($c, $idx,$offs, $txt, $asc) = (0, 0, 0, "", ""); my $dlen = length(sprintf $bfmt, 255) + 1; my $half = ($count % 2)? 999: ($count / 2); # Adjust the filepointer to the start. If it's an actual file, seek # will work, otherwise a total of $start bytes must be discarded first. # if ($start) { if (!$b_stdin) { seek($fh, 0, $offs = $start); } else { while ($offs < $start) { defined($c = getc($fh)) or return; ++$offs; } } } # Define closure to process each byte of data my $pinsert = sub { my ($byte) = @_; if (defined($byte) && ($end < 0 || ($start + $idx) <= $end)) { $idx++; $txt .= sprintf $bfmt, $byte; $txt .= ($idx % $count)? ($idx % $half)? " ": $mid: ""; $asc .= ($byte < 32 || $byte > 126)? $bin: chr($byte); ($idx and 0 == $idx % $count) and $byte = undef; } if (!defined($byte)) { if ($txt) { my $pad = 0; while ($idx % $count) { $pad += $dlen; ++$idx; } $txt .= " " x ($pad - 1); my $out = sprintf $afmt, $offs; $out .= sprintf "%s%s%s%s\n", $left, $txt, $right, $asc; $pout and $$pout .= $out; $pout or print $out; $offs += $count; } $txt = $asc = ""; } }; # Process the file print "\n"; while (($max < 0 or $max--) and defined($c = getc($fh))) { $pinsert->(ord $c); } # Dump any final data, and return the filehandle $pinsert->(); return $fh; }