Fellow brethren,

several other monks as well as myself have occasionally remarked on the usefulness of the *NIX lsof utility. Unfortunately, no module existed that offered a nice interface for running lsof and conveniently extracting relevant information from its output, so after wishing for one to spring from the ground for a while I went ahead and wrote one. Here it is. Before uploading it to CPAN I'd like to solicit any criticism and comments you might have to offer. In particular I'd very much appreciate comments on the following questions:

  1. Is the name appropriate? Choosing Unix::Lsof seemed the obvious thing to do, but the module is a wrapper around the existing lsof binary, so should it perhaps be called Unix::Lsof::Wrapper or something, just in case somebody ever comes along who wants to reimplement lsof in pure perl (unlikely IMHO)?
  2. In attempting DWIMmery I made the accessor methods for Unix::Lsof::Result return either lists or array/hash references, depending on the context in which they are called. Is this actually more confusing than helpful?
  3. The accessor methods are somewhat inaccurately named. For example, get_arrayof_rows returns an array of arrays, whereas get_hashof_rows returns a hash, the values of which are references to arrays of hashes. Is this adequately explained in the docs? Can you think of better names for the methods?
  4. Is using spaces within hash keys (which means users will have to type $result{"file type"} instead of just $result{file_type} ) stupid?

Any other comments/criticisms, be they positive or negative are also more than welcome. Below are the two modules that make up the distribution, if you want to download the complete distribution with tests/Makefile etc. you can currently get it here Update: changed link, now up on CPAN, thanks for everyones help.(will change that to a CPAN link once the module is uploaded).

Update: almut already discovered a bug. Fixed now.

Unix::Lsof :
package Unix::Lsof; use 5.008; use version; our $VERSION = qv('0.0.1'); use warnings; use strict; use IPC::Run3; use Unix::Lsof::Result; use base qw(Exporter); our @EXPORT = qw(lsof); our @EXPORT_OK = qw(parse_lsof_output); our %op_field = ( a => q(access mode), c => q(command name), C => q(structure share count), d => q(device character code), D => q(major/minor device number), f => q(file descriptor), F => q(structure address), G => q(flags), i => q(inode number), k => q(link count), l => q(lock status), L => q(login name), n => q(file name), N => q(node identifier), o => q(file offset), g => q(process group id), p => q(process id), P => q(protocol name), r => q(raw device number), R => q(parent pid), s => q(file size), S => q(stream module and device names), t => q(file type), T => q(tcp/tpi info), u => q(user id), z => q(zone name) ); sub lsof { my @arg = @_; # TODO: split if only one argument is passed, so that a shell line # can be used as-is my ( @params, $lsof_bin ); if ( ref $arg[0] eq "HASH" ) { $lsof_bin = $arg[0]->{binary} || _find_binary(); @params = _construct_parameters( $arg[0] ); } else { $lsof_bin = _find_binary(); @params = @arg; } if ( !-e $lsof_bin ) { die "Cannot find lsof program $lsof_bin: $!"; } if ( !-x $lsof_bin || !-f $lsof_bin ) { die "$lsof_bin is not an executable binary: $!"; } my ( @out, $err ); eval { run3( [ $lsof_bin, "-F0", @params ], \undef, \@out, \$err ) +; }; if ($@) { $err = $err ? $@ . $err : $@; } my $parsed = parse_lsof_output( \@out ); if (wantarray) { return ( $parsed, $err ); } else { return Unix::Lsof::Result->_new( $parsed, $err, \@out ); } } sub _find_binary { my @path = split( ":", $ENV{PATH} ); my $bin; PATHLOOP: for my $p (@path) { if ( -e $p . "/lsof" ) { $bin = $p . "/lsof"; last PATHLOOP; } } return $bin; } # This is a stub for now. Constructing lsof arguments is a little # tricky and will be done conclusively later sub _construct_parameters { my $options = shift; my @cmd_line; my %translate = ( pid => "-p", file => undef ); for my $arg ( keys %{$options} ) { if ( exists $translate{$arg} ) { push @cmd_line, $translate{$arg} if ( defined $translate{$ +arg} ); push @cmd_line, $options->{$arg}; } } return scalar @cmd_line ? @cmd_line : undef; } sub parse_lsof_output { my $output = shift; my ( %result, $pid ); for my $line (@$output) { my @elements = split( "\0", $line ); my $first = $elements[0]; if ( $first =~ m/^p(\d+)$/ ) { $pid = $1; $result{$pid} = _parseelements( \@elements ); } elsif ( $first =~ m/^f(.*)$/ ) { push @{ $result{$pid}{files} }, _parseelements( \@elements + ); } else { die "Can't parse line $line"; } } return \%result; } sub _parseelements { my $elements = shift; my %result; while ( my $elem = shift @$elements ) { my ( $fident, $content ) = ( $elem =~ /^(.)(.*)$/ ); next if !$fident; $result{ $op_field{$fident} } = $content; } return \%result; } 1; __END__ =head1 NAME Unix::Lsof - Wrapper to the Unix lsof utility =head1 VERSION This document describes Unix::Lsof version 0.0.1 =head1 SYNOPSIS use Unix::Lsof; my ($output,$error) = lsof("afile.txt"); my @pids = keys %$output; my @commands = map { $_->{"command name"} } values %$output; ($output,$error) = lsof("-p",$$); my @filenames; for my $pid (keys %$output) { for my $files ( @{ $o->{$k}{files} } ) { push @filenames,$f->{"file name"} } } my $lr = lsof ("-p",$$); # see Unix::Lsof::Result @filenames = $lrs->get_filenames(); @inodes = $lrs->get_values("inode number"); =head1 DESCRIPTION This module is a wrapper around the Unix lsof utility (written by Vict +or A.Abel, Copyright Purdue University), which lists open files as well as inform +ation about the files and processes opening them. C<Unix::Lsof> uses the lso +f binary, so you need to have that installed in order to use this module. By default, this module exports a single function C<lsof>, to which yo +u can pass the same parameters you would the lsof binary. When called in list con +text, C<lsof> will return two values, a hash reference containing the parsed + output of the lsof binary and a string containing (unparsed) any error messag +es. When called in scalar context, C<lsof> will return a C<Unix::Lsof::Result> +object (see the documentation for that module for further details). On request, you can also export the subroutine C<parse_lsof_output> wh +ich will do what the name says and return the parsed output. =head1 INTERFACE =head2 Subroutines =over 4 =item lsof( [PARAMETERS] ) C<lsof> accepts parameters passed on to the lsof binary. These need to + be in list form, so you need to do $r = lsof("-p",$pid,"-a","+D","/tmp"); B<NOT> $r = lsof("-p $pid -a +D /tmp"); SCALAR CONTEXT Example: $lr = lsof("afile"); When called in scalar context, C<lsof> will return a C<Unix::Lsof::Res +ult> object. Please see the documentation for that module on how to use thi +s object. I mean it, really, take a look there, you'll almost certainly want to +use the C<Unix::Lsof::Result> interface which gives you lots of helper methods + to dig out exactly the information you want. LIST CONTEXT Example: ($output,$error) = lsof("afile"); When called in list context, C<lsof> will return two values, a hash re +ference containing the parsed output of the lsof binary, and a string containi +ng any error messages. The output data structure looks like this (approximati +ng C<Data::Dumper> formatting here:) $output = { '<pid>' => { '<process field name>' => '<value>', '<process field name>' => '<value>', 'files' => [ { '<file field name>' => '<va +lue>', '<file field name>' => '<va +lue>' }, { '<file field name>' => '<va +lue>', '<file field name>' => '<va +lue>' ... }, ] } ... } Each process id (pid) is the key to a hash reference that contains as +keys the names of process set fields and their corresponding field value. The s +pecial C<files> key has an array reference as value. Each element in this arr +ay is a hash reference that contains the names of file set fields and their va +lues. See the section OUTPUT FOR OTHER PROGRAMS in the lsof manpage if you're wo +ndering what process and file sets are. Process field names are: "command name" "login name" "process id" "process group id" "parent pid" "user id" File field names are: "access mode" "structure share count" "device character code" "major/minor device number" "file descriptor" "structure address" "flags" "inode number" "link count" "lock status" "file name" "node identifier" "file offset" "protocol name" "raw device number" "file size" "stream module and device names" "file type" "tcp/tpi info" "user id" "zone name" =item parse_lsof_output ( <STRING> ) This function takes the raw output as obtained from the lsof binary (w +ith the -F0 option) and parses it into the data structure explained above. It +does B<not> understand the lsof STDERR output. =back =head1 DIAGNOSTICS =over =item C<< Cannot find lsof program >> Couldn't find the lsof binary which is required to use this module. Ei +ther install lsof or (if it's installed) be sure that it is in your shells +path. =item C<< Can't parse line >> Encountered a line of lsof output which could not be properly parsed. +If you get this from calling C<lsof()> it is almost certainly a bug, please l +et me know so I can fix it. If you encountered it from running C<parse_lsof_outpu +t>, please make sure that the output was obtained from running lsof with the -F0 +option. =back =head1 CONFIGURATION AND ENVIRONMENT Unix::Lsof requires no configuration files. It searches for the lsof b +inary in the directories listed in your $PATH environment variable. =head1 DEPENDENCIES Unix::Lsof requires the following modules: =over =item * version =item * IPC::Run3 =back =head1 INCOMPATIBILITIES None reported. =head1 BUGS AND LIMITATIONS Please report any bugs or feature requests to C<bug-unix-lsof@rt.cpan.org>, or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Unix-Lsof>. No bugs have been reported so far. There are a number of improvements +that could be made to this module, particularly in the handling of parameters. Fu +rther development is almost certainly strictly "develop by bugreport", so if + you want a feature added, please open a wishlist item in the RT web interface a +nd let me know. I'm more than happy to do more work on this module, but want to +see what concrete improvements people would like to have. As always, patches ar +e more than welcome. =head1 ACKNOWLEDGEMENTS A very heartfelt thanks to Vic Abel for writing C<lsof>, it has been i +nvaluable to me over the years. Many thanks as always to http://www.perlmonks.or +g, the monks continue to amaze and enlighten me. A very special thanks to Dam +ian Conway, who (amongst other things) recommends writing module documenta +tion at the same time as code (in his excellent book "Perl Best Practices") +. I didn't follow that advice and as a result writing these docs was more painful + and error-prone than it should have been. Please Mr. Conway, for the next +edition could you put more emphasis on that recommendation so that dolts like +me get it the first time? =head1 AUTHOR Marc Beyer C<< <japh@tirwhan.org> >> =head1 LICENCE AND COPYRIGHT Copyright (c) 2008, Marc Beyer C<< <japh@tirwhan.org> >>. All rights r +eserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L<perlartistic>. =head1 DISCLAIMER OF WARRANTY BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WH +EN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. TH +E ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
Unix::Lsof::Result :
package Unix::Lsof::Result; use 5.008; use version; our $VERSION = qv('0.0.1'); use warnings; use strict; use Unix::Lsof; use overload bool => sub { my ($self) = @_; if ( $self->{error} && (!keys %{$self->{output}}) ) { return; } else { return 1; } }; sub _new { my ( $class, $parsed, $err, $raw ) = @_; my $self = { output => $parsed, error => $err, _raw_output => $raw, }; bless $self, $class; $self->_get_field_ids(); return $self; } sub has_errors { my $self = shift; return $self->{error} ? 1 : 0; } sub errors { my $self = shift; return $self->{error}; } sub get_pids { my $self = shift; my @params = ( "process id"); unshift @params,$_[0] if ref( $_[0] ); my @ret =$self->get_values( @params ); return wantarray ? @ret : \@ret; } sub get_filenames { my $self = shift; my @params = ( "file name" ); unshift @params,$_[0] if ref( $_[0] ); my @ret = $self->get_values( @params ); return wantarray ? @ret : \@ret; } sub get_values { my ( $self, @args ) = @_; my @col = $self->get_arrayof_columns(@args); return if (!defined $col[0]); return wantarray ? @{$col[0]} : $col[0]; } sub get_arrayof_columns { my ( $self, @args ) = @_; my @rows = $self->get_arrayof_rows(@args); my @cols; my $i = 0; for my $r (@rows) { my $j = 0; for my $c (@$r) { $cols[ $j++ ][$i] = $c; } $i++; } return wantarray ? @cols : \@cols; } sub get_hashof_columns { my ( $self, @args ) = @_; my @cols = $self->get_arrayof_columns(@args); my %return; for my $i ( 0 .. $#cols ) { $return{ $args[$i] } = $cols[$i]; } return wantarray ? %return : \%return; } sub get_hashof_rows { my ( $self, @args ) = @_; my ( $key, %ret ); if ( ref( $args[0] ) eq ref( {} ) ) { $self->{_query}{filter} = shift @args; } ( $key, @{ $self->{_query}{inp_fields} } ) = @args; my $full_key = $Unix::Lsof::op_field{$key} || $key; $self->_setup_fields(); $self->_setup_filter(); my %outp = %{ $self->{output} }; my %uniqify; for my $pid ( keys %outp ) { LINELOOP: for my $file ( @{ $outp{$pid}{files} } ) { my $hkey = $self->_get_value( $full_key, $pid, $file ); my $line = $self->_get_line( $pid, $file ) || next LINELOO +P; my $i = 0; my %rline; for my $l (@$line) { if (defined $l) { $rline{ $self->{_query}{inp_fields}[ $i ] } = $l; } $i++; } push @{ $ret{$hkey} }, \%rline if ( !$uniqify{$hkey}{ join $;, sort values %rline }++ + ); } } delete $self->{_query}; return wantarray ? %ret : \%ret; } sub get_arrayof_rows { my ( $self, @args ) = @_; my @ret; if ( ref( $args[0] ) eq ref( {} ) ) { $self->{_query}{filter} = shift @args; } $self->{_query}{inp_fields} = \@args; $self->_setup_fields(); $self->_setup_filter(); my %outp = %{ $self->{output} }; my %uniqify; for my $pid ( keys %outp ) { ROWLOOP: for my $file ( @{ $outp{$pid}{files} } ) { my $line = $self->_get_line( $pid, $file ) || next ROWLOOP +; push @ret, $line if ( !$uniqify{ join $;, map { defined $_ + ? $_ : "" } @$line }++ ); } } delete $self->{_query}; return wantarray ? @ret : \@ret; } sub _setup_filter { my $self = shift; return if ( !exists $self->{_query}{filter} ); %{ $self->{_query}{filter} } = map { $Unix::Lsof::op_field{$_} || $_ => $self->{_query}{filte +r}{$_} } keys %{ $self->{_query}{filter} }; my %check_filter = map { $_ => 1 } @{ $self->{_query}{ret_fields} +}; @{ $self->{_query}{force_validate} } = grep { !exists $check_filte +r{$_} } keys %{ $self->{_query}{filter} }; } sub _setup_fields { my $self = shift; my ( @temp_fields, @ret_fields ); # Use either field designators or names @ret_fields = map { $Unix::Lsof::op_field{$_} || $_ } @{ $self->{_query}{inp_fields} }; for my $f (@ret_fields) { if ( exists $self->{_program_field_ids}{$f} ) { push @temp_fields, $f; } elsif ( exists $self->{_file_field_ids}{$f} || $f eq "proces +s id" ) { push @temp_fields, $f; } else { warn "$f is not in the list of fields returned by lsof"; } } $self->{_query}{ret_fields} = \@temp_fields; } sub _validate { my ( $self, $filter_key, $value ) = @_; my $filter = $self->{_query}{filter}{$filter_key}; return if !defined $value; if ( ref($filter) eq ref( sub { } ) ) { return $filter->($value); } elsif ( ref($filter) eq ref(qr//) ) { return $value =~ $filter ? 1 : 0; } elsif ( $filter =~ m/\A\d+\z/ && $value =~ m/\A\d+\z/) { return $filter == $value ? 1 : 0; } elsif ( !ref($filter) ) { return $filter eq $value ? 1 : 0; } else { warn qq(Invalid filter specified for "$filter_key"); } } sub _get_line { my ( $self, $pid, $file ) = @_; my @line; for my $force ( @{ $self->{_query}{force_validate} } ) { my $val = $self->_get_value( $force, $pid, $file ); $self->_validate( $force, $val ) || return; } for my $field ( @{ $self->{_query}{ret_fields} } ) { my $val = $self->_get_value( $field, $pid, $file ); if ( exists( $self->{_query}{filter}{$field} ) ) { $self->_validate( $field, $val ) || return; } push @line, $val; } return \@line; } sub _get_value { my ( $self, $name, $pid, $file ) = @_; my $ret = exists $self->{_program_field_ids}{$name} ? $self->{output}{$pid}{$name} : $file->{$name}; return $ret; } sub _get_field_ids { my $self = shift; for my $pid ( keys %{ $self->{output} } ) { for my $pfield ( keys %{ $self->{output}{$pid} } ) { if ( $pfield eq "files" ) { for my $file ( @{ ${ $self->{output} }{$pid}{files} } +) { ++$_ for @{ %{ $self->{_file_field_ids} } }{ keys % +$file }; } } else { $self->{_program_field_ids}{$pfield}++; } } } } =head1 NAME Unix::Lsof::Result - Perlish interface to lsof output =head1 VERSION This document describes Unix::Lsof::Result version 0.0.1 =head1 SYNOPSIS use Unix::Lsof; my $lr = lsof("-p",$$); if ($lr->has_errors()) { print qq(Errors encountered: $lr->errors()); } my @pids = $lr->get_pids(); my @file_types = $lr->get_values( "file type" ); my $access_modes = $lr->get_values( "access mode" ); # Print out file name and type my @filenames = $lr->get_arrayof_rows( "file type", "file name" + ); for my $p (@filenames) { print "File type: $p->[0] - File name: $p->[1]\n"; } # Print a list of open IPv4 connections my %filetype = $lr->get_hashof_rows( "file type", "n", "protoc +ol name" ); for my $conn ( @{ $filetype{"IPv4"} } ) { print qq(IPv4 connection to: $conn->{"n"}, protocol: $conn->{" +protocol name"}\n); } # Print out a list of open files larger than 1kb my @filesize = $lr->get_arrayof_columns( "file name", "file si +ze" ); for my $i ( 0..scalar( @{ $filesize[1] } ) ) { if ( $filesize[1][$i] >= 1024 ) { print "File $filesize[0][$1] is over 1k\n"; } } # Print out the size of text files found my $fs = $lr->get_hashof_columns( "file name", "file siz +e" ); for my $i ( 0..scalar( @{ $fs->{"file name"} } ) ) { if ( $fs->{"file name"}[$i] =~ m/\.txt\z/ ) { print qq(Found $fs->{"file size"}[$i] bytes large text fil +e\n); } } # The same as previous, using filters my @file_list = $lr->get_values( { "file name" => qr/\.txt\z/ } +, "file size" ); for my $f (@file_list) { print qq(Found $f bytes large text file); } # Looking for text files between 1 and 4k my @file_list = $lr->get_filenames( { "file name" => qr/\.txt\z +/, "file size" => sub { $_[ +0] > 1024 && + $_[0] <= 4096 } }, "file name"); =head1 DESCRIPTION This module offers multiple ways of organising and retrieving the data + returned by lsof. It attempts to make it as easy as possible for the user to ob +tain the information he wants in the way he wants it. The C<Unix::Lsof::Result> object is returned when calling C<Unix::Lsof +->lsof()> in scalar context. When evaluated in boolean context the object will e +valuate to true unless no STDOUT output is obtained from running the C<lsof> bina +ry AND STDERR output was obtained from the same run. This allows for the foll +owing logic : if ($lf = $lsof(@params)) { # we got output or no errors, some success was had warn "Errors: ".$lf->errors() if $lf->has_errors(); # normal processing continues # ... } else { # no output and we have an error message, something is badly w +rong die "Errors: ".$lf->errors(); } Note that you will only find out whether B<any> errors were encountere +d by examining the C<has_errors> return value, examining truth of the objec +t itself only makes sense if you just care about some valid output being return +ed (e.g. if you're passing in a list of files, some of which may not exist). All of the output accessor methods (i.e. the methods starting with C<g +et_>, for example C<get_values>, C<get_arrayof_rows>) have the following propert +ies: =over 4 =item * Only return unique values Also, all output accessor methods will only return unique result sets, + e.g. if a single file is opened by multiple programs $lf->get_arrayof_rows( "file name", "process id"); will return as many rows as there are processes opening the file, wher +eas $lf->get_arrayof_rows( "file name", "file type"); will only return a single row, since file name and file type are the s +ame for all return sets. =item * Returns list or reference depending on calling context The accessor methods are sensitive to their calling context and will r +eturn either a list or a reference to an array/hash. # This will return a list @e = $lf->get_pids(); # This will return an array reference $e = $lf->get_pids() =item * Fields can be specified either with their full name or single characte +r When specifying a list of fileds which you want returned from the acce +ssor, you can either use the single character associated with that field (see th +e lsof man page section "OUTPUT FOR OTHER PROGRAMS" for a list of these) or the f +ull field name as given in ther C<Unix::Lsof> perldoc. =item * Filters The method can optionally be provided with a "filter" which limits the data returned. For this, pass a hash reference as the first argument, +of the format { <field name> => <filter>, <field name> => <filter>, ... } where <filter> can be either a scalar value, a reference to a regular +expression or a reference to a subroutine. Only record sets that match on all fie +lds will be returned. A subroutine must return true to "match", the field value + is passed in normally as the first element of the @_ array. Example: { "process id" => 4242, "n" => qr/\.txt\z/i, "command name" => sub { $_[0] =~ m/kde/ || $_[0] eq "cupsd" } } Limitations: is is very well possible to specify a filter that complet +ely excludes any files, e.g. { "process id" => "-1" } . Also, to specify more than one condition on a given field name (e.g. + greater than 100 but not 105) you need to use a sub e.g. { "process id" => sub { $_[0] > 100 && $_[0] != 105 } } The same goes if you need to C<or> a set of constraints. Note that the +re is currently no way to C<or> constraints over several fields (e.g. proces +s id equals 1000 or user id equals 42). =back =head1 INTERFACE =head2 Methods =over 4 =item C<has_errors> $lf->has_errors(); Returns true if the call to the lsof binary returned any STDERR output +. =item C<errors> $lf->errors(); Returns the STDERR output of the lsof binary in a single string. WARNI +NG: it is possible that this B<may> change in some future version to allow for m +ore sophisticated error handling (though using the result of this subrouti +ne as a simple string will almost certainly continue to be supported). =item C<get_values> $lf->get_values( $field_name ); $lf->get_values( $filter, $field_name ); Returns a list with the values for a single field. =item C<get_pids> $lf->get_values(); $lf->get_values( $filter ); Specialised version of get_values which returns a list of process ids. =item C<get_filenames> $lf->get_filenames(); $lf->get_filenames( $filter ); Specialised version of get_values which returns a list of file names. =item C<get_arrayof_rows> $lf->get_arrayof_rows( @column_names ); $lf->get_arrayof_rows( $filter, @column_names ); Returns a list of array references, each of which corresponds to a row + of lsof output. The order of the values in the referenced arrays corresponds t +o the order of the parameters passed in, e.g. a call to $lf->get_arrayof_rows( "file name", "file type"); would produce a data structure like this [ [ filename1, filetype1 ], [ filename2, filetype2 ], ... ] =item C<get_arrayof_columns> $lf->get_arrayof_columns( @column_names ); $lf->get_arrayof_columns( $filter, @column_names ); Returns a list of array references, each of which correspond to a fiel +d name column. The order of array references correspond to the order of param +eters passed in, e.g. a call to $lf->get_arrayof_columns( "file name", "file type"); would produce a data structure like this [ [ filename1, filename2, ... ], [ filetype1, filetype2, ... ] ] =item C<get_hashof_columns> $lf->get_hashof_columns( @column_names ); $lf->get_arrayof_columns( $filter, @column_names ); Returns a hash references (or list which can be assigned to a hash). T +he hash keys are the column names specified in the parameters, the hash values + are array references with the column values. E.g. a call to $lf->get_hashof_columns( "file name", "file type"); would produce a data structure like this { "file name" => [ filename1, filename2 ], "file type" => [ filetype1, filetype2 ] } The hash keys returned are exactly of the same format as passed in via + the parameters, so passing in a single character will B<not> create a full + field name key. E.g. $lf->get_hashof_columns( "file name", "t"); will produce this { "file name" => [ filename1, filename2 ], "t" => [ filetype1, filetype2 ] } =item C<get_hashof_rows> $lf->get_hashof_rows( $key, @column_names ); $lf->get_arrayof_rows( $filter, $key, @column_names ); Returns a hash reference (or a list which can be assigned to a hash). +The hash keys are the value of the field which is given as the first parameter. + The hash values are references to arrays, each of which contain a row of the re +quested fields in a hash e.g. a call to $lf->get_hashof_rows( "process id", "file name", "t" ); would produce a data structure like this { pid1 => [ { "file name" => filename1, "t" => filetype1 }, { "file name" => filename2, "t" => filetype2 }, ], pid2 => [ { "file name" => filename3, "t" => filetype3 } ] } =back =head1 DIAGNOSTICS =over =item C<< %s is not in the list of fields returned by lsof >> You requested a field which was not in the list of fields returned by +the lsof binary. Check that you spelt the field name correctly and that it is i +n the list of field names specified in the C<Unix::Lsof> docs. Also check that th +e field name is supported on the platform you are running lsof on. =item C<< Invalid filter specified for "%s" >> You specified an invalid filter. Valid filters are strings, numbers or references to regular expressions or subroutines. See the documentatio +n on "Filters" above. =back =head1 CONFIGURATION AND ENVIRONMENT Unix::Lsof::Result requires no configuration files or environment vari +ables. =head1 DEPENDENCIES Unix::Lsof requires the following modules: =over =item * version =back =head1 INCOMPATIBILITIES None reported. =head1 BUGS AND LIMITATIONS Please report any bugs or feature requests to C<bug-unix-lsof@rt.cpan.org>, or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Unix-Lsof>. No bugs have been reported so far. As with C<Unix::Lsof>, there are a +number of improvements that could be made to this module, particularly with rega +rds to filtering. Further development is almost certainly strictly "develop b +y bugreport", so if you want a feature added, please open a wishlist ite +m in the RT web interface and let me know. I'm more than happy to do more work +on this module, but want to see what concrete improvements people would like t +o have. As always, patches are more than welcome. =head1 ACKNOWLEDGEMENTS A very heartfelt thanks to Vic Abel for writing C<lsof>, it has been i +nvaluable to me over the years. Many thanks as always to http://www.perlmonks.or +g, the monks continue to amaze and enlighten me. A very special thanks to Dam +ian Conway, who (amongst other things) recommends writing module documenta +tion at the same time as code (in his excellent book "Perl Best Practices") +. I didn't follow that advice and as a result writing these docs was more painful + and error-prone than it should have been. Please Mr. Conway, for the next +edition could you put more emphasis on that recommendation so that dolts like +me get it the first time? =head1 AUTHOR Marc Beyer C<< <japh@tirwhan.org> >> =head1 LICENCE AND COPYRIGHT Copyright (c) 2007, Marc Beyer C<< <japh@tirwhan.org> >>. All rights r +eserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L<perlartistic>. =head1 DISCLAIMER OF WARRANTY BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WH +EN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. TH +E ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

All dogma is stupid.

Replies are listed 'Best First'.
Re: RFC: Unix::Lsof
by almut (Canon) on Feb 24, 2008 at 22:46 UTC

    First, thanks for writing the module!  As to the name, I think Unix::Lsof is fine — if anyone wants to reimplement it in pure Perl, they could still name it Unix::Lsof::Perl (which would be in line with the naming convention used with Net::SSH::Perl).

    I have no definitive opinion regarding 2. and 3. (IOW, it's fine with me as you have it :), but I would personally rather avoid spaces in hash keys (4.).

    Running make test on my Debian box (etch, i.e. lsof 4.77) with Perl 5.8.8 gives:

    $ make test PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_h +arness(0, 'blib/lib', 'blib/arch')" t/*.t t/10.procedural.....ok + 5/12 skipped: Test::Exception not installed t/20.lsof-result....ok 1/50 + # Failed test 'returns true if the file exists' # in t/20.lsof-result.t at line 27. # Failed test 'Reports no errors when there aren't any' # in t/20.lsof-result.t at line 28. t/20.lsof-result....NOK 11 + # Failed test 'returns true if the file is open' # in t/20.lsof-result.t at line 34. t/20.lsof-result....NOK 12 + # Failed test 'returns true when called on process' # in t/20.lsof-result.t at line 37. t/20.lsof-result....NOK 14# Looks like you failed 4 tests of 50. + t/20.lsof-result....dubious + Test returned status 4 (wstat 1024, 0x400) DIED. FAILED tests 10-12, 14 Failed 4/50 tests, 92.00% okay (less 1 skipped test: 45 okay, +90.00%) t/pod-coverage......skipped all skipped: Test::Pod::Coverage 1.04 required for testing POD + coverage t/pod...............skipped all skipped: Test::Pod 1.14 required for testing POD Failed Test Stat Wstat Total Fail Failed List of Failed ---------------------------------------------------------------------- +--------- t/20.lsof-result.t 4 1024 50 4 8.00% 10-12 14 2 tests and 6 subtests skipped. Failed 1/4 test scripts, 75.00% okay. 4/62 subtests failed, 93.55% oka +y. make: *** [test_dynamic] Error 255

    I'd be happy to provide more info (should you want it) to resolve the remaining issues... Feel free to /msg me.

Re: RFC: Unix::Lsof
by wazoox (Prior) on Feb 26, 2008 at 18:35 UTC
    This is a nice module. I had an harmless error while testing on my Slackware 12.0 box ( perl is a symlink to perl5.8.8 ):
    emmanuel[~/temp/Unix-Lsof]$ make test PERL_DL_NONLAZY=1 /usr/bin/perl5.8.8 "-MExtUtils::Command::MM" "-e" "t +est_harness(0, 'blib/lib', 'blib/arch')" t/*.t t/10.procedural.......ok t/20.lsof-result......1/55 # Failed test 'Correct command name' # at t/20.lsof-result.t line 75. # got: 'perl5.8.8' # expected: 'perl' t/20.lsof-result......54/55 # Looks like you failed 1 test of 55. t/20.lsof-result...... Dubious, test returned 1 (wstat 256, 0x100) Failed 1/55 subtests t/pod-coverage........ok t/pod.................ok Test Summary Report ------------------- t/20.lsof-result.t (Wstat: 256 Tests: 55 Failed: 1) Failed test: 23 Non-zero exit status: 1 Files=4, Tests=71, 2 wallclock secs ( 0.05 usr 0.01 sys + 0.63 cusr + 0.64 csys = 1.33 CPU) Result: FAIL Failed 1/4 test programs. 1/71 subtests failed. make: *** [test_dynamic] Erreur 255

      Thanks! I fixed the test to pass on your platform as well and uploaded. Since nobody seems to object to the name I think I'll register a namespace tomorrow and upload it to CPAN.


      All dogma is stupid.

        Though I prefer Unix::lsof (no, it doesn't look like a pragma, there is a capital 'U' -- at least that is the only motivation I could imagine for one capitalizing the 'l').

        - tye