Эээ... Эт сликом сложно 

. И опять же не то, что надо. Мне бы с фиксированными полями. Вот накидал уже
TextDB.pm
--
package TextDB;
sub new
{
    my ($class,%args) = @_;
    my $self = bless {}, $class;
    $self->{opened} = 0;
    $self->{filename} = $args{filename};
    $self->{fields} = {}; # map
    $self->{values} = []; # values
    $self->{recordsize} = 0;
    $self->{recordcount} = 0;
    $self->{filemode} = $args{filemode} || ">";
    my $fh;
    if (open($fh,$self->{filemode}.$self->{filename}))
    {
        $self->{opened} = 1;
        $self->{fh} = $fh;
        if ($self->{lockmode})
        {
            _flock($fh,$self->{lockmode});
        }
        my $head = $args{head};
        my $format = "";
        my $fieldlist = "";
        my $i = 0;
        for $fieldpair (@{$head})
        {
            my ($field,$length) = split(\',\',$fieldpair);
            $format .= "A".$length;
            if ($fieldlist eq "")
            {
                $fieldlist = $field;
            }
            else
            {
                $fieldlist .= ",".$field;
            }
            $self->{fields}{$field} = $i;
            $self->{values}[$i] = \'\';
            $self->{recordsize} += 0 + $length;
            $i++;
        }
        $self->{recordsize}++;
        print $fh $format.chr(13);
        print $fh $fieldlist.chr(13);
        $self->{format} = $format;
        if ($args{autoclose})
        {
            close($fh);
            undef($self->{fh});
            $self->{opened} = 0;
        }
        else
        {
            $self->{dataoffset} = tell($fh);
        }
    }
    return $self;
}
sub opendb
{
    my ($class,%args) = @_;
    my $self = bless {}, $class;
    $self->{opened} = 0;
    $self->{filename} = $args{filename};
    $self->{fields} = {}; # map
    $self->{values} = []; # values
    $self->{recordsize} = 0;
    $self->{format} = "";
    $self->{filemode} = $args{filemode} || "<";
    my $fh;
    if (open($fh,$self->{filemode}.$self->{filename}))
    {
        $self->{opened} = 1;
        $self->{fh} = $fh;
        if ($self->{lockmode})
        {
            _flock($fh,$self->{lockmode});
        }
        my $sore_nl = $/;
        $/ = chr(13);
        chomp(my $format = <$fh>);
        chomp(my $fieldlist = <$fh>);
        $/ = $store_nl;
        my @lengths = split(\'A\',substr($format,1));
        $self->{format} = $format;
        for (@lengths)
        {
            $self->{recordsize} += 0 + $_;
        }
        $self->{recordsize}++;
        $self->{recordcount} = int(((-s $self->{filename}) - $self->{dataoffset}) / $self->{recordsize});
        my @fieldlist = split(\',\',$fieldlist);
        my $i = 0;
        for $field (@fieldlist)
        {
            $self->{fields}{$field} = $i;
            $self->{values}[$i] = \'\';
            $i++;
        }
        $self->{dataoffset} = tell($fh);
    }
    return $self;
}
sub field
{
    my $self = shift;
    my $fieldname = shift;
    my $fieldno = $self->{fields}{$fieldname};
    my $value = $self->{values}[$fieldno];
    if (@_) { $self->{values}[$fieldno] = shift;}
    return $value;
}
sub append
{
    my $self = shift;
    if (@_)
    {
       my %values = @_;
       while (my ($field,$value) = each(%values))
       {
           $self->{values}[$self->{fields}{$field}] = $value;
       }
    }
    seek($self->{fh},0,2);
    print {$self->{fh}} pack($self->{format},@{$self->{values}});
    print {$self->{fh}} chr(13);
    $self->{recordcount}++;
}
sub close
{
    if ($self->{opened})
    {
        close($self->{fh});
        undef($self->{fh});
        $self->{opened} = 0;
    }
}
sub read
{
    my ($self,$pos) = @_;
    if ($self->{opened})
    {
        seek($self->{fh},$self->{dataoffset}+$pos*$self->{recordsize},0);
        my $fh = $self->{fh};
        my $record = <$fh>;
        @{$self->{values}} = unpack($self->{format},$record);
    }
}
sub update
{
    my ($self,$pos,$record) = @_;
    $self->read($pos);
    while (my ($field,$value) = each(%$record))
    {
        $self->{values}[$self->{fields}{$field}] = $value;
    }
    seek($self->{fh},-$self->{recordsize},1);
    print {$self->{fh}} pack($self->{format},@{$self->{values}});
    print {$self->{fh}} chr(13);
}
return true;
--
testdb.pl
--
use lib \'F:\\Apache\\sites\\test\\cgi-bin\';
use TextDB;
print "-- New --\\n";
$tdb = new TextDB(
        filename => \'f:\\test.db\',
        head => ["field1,10","field2,10","field3,20"]
);
$tdb->field("field1","It\'s a field 1");
$tdb->field("field2",2);
$tdb->field("field3","field 3");
print "field1 = ".$tdb->field("field1");
print "\\n";
print "field2 = ".$tdb->field("field2");
print "\\n";
print "field3 = ".$tdb->field("field3");
print "\\n";
$tdb->append();
$tdb->append(field1=>"one two three",field3=>100);
print "offset = ".$tdb->{dataoffset};
print "\\n";
print "rec size = ".$tdb->{recordsize};
print "\\n";
$tdb->close();
undef($tdb);
print "-- Open --\\n";
$tdb = opendb TextDB(filename => \'f:\\test.db\',filemode => "+<");
print "offset = ".$tdb->{dataoffset};
print "\\n";
print "rec size = ".$tdb->{recordsize};
print "\\n";
$tdb->append(field1=>20,field2=>"open test");
for ($i=0;$i < $tdb->{recordcount};$i++)
{
    print "record # $i\\n";
    $tdb->read($i);
    print "field1 = ".$tdb->field("field1");
    print "\\n";
    print "field2 = ".$tdb->field("field2");
    print "\\n";
    print "field3 = ".$tdb->field("field3");
    print "\\n";
}
$tdb->update(2,{field3 => \'updated\'});
print "-- Update -- \\n";
for ($i=0;$i < $tdb->{recordcount};$i++)
{
    print "record # $i\\n";
    $tdb->read($i);
    print "field1 = ".$tdb->field("field1");
    print "\\n";
    print "field2 = ".$tdb->field("field2");
    print "\\n";
    print "field3 = ".$tdb->field("field3");
    print "\\n";
}
$tdb->close();
Правда остается проблема с особождением хэндла. Так и не хочет сволочь освобождать файл по нормальному. Приходится делать undef($tdb). Это более менее терпимо, но всеравно неприятно