walkingthecow has asked for the wisdom of the Perl Monks concerning the following question:

I have a subroutine that gets a user's information from the /etc/shadow file and returns the following information about the user: locked/unlocked, pw_min_time, pw_max_time, pw_warn_time, inactive time, and expire date.

My question is this... How do I have a subroutine that returns all those values, but allows users to specify what values to return. I have it right now return all values, and user could call it like this:

my ($locked,undef,undef,undef,undef,$expire)=$ssh->get_user_info($logi +nid);
However, I'd much rather user's be able to do something like this:
my ($locked_status,$expire)=$ssh->get_user_info($loginid); my $inactive=$ssh->get_user_info($loginid);
In other words, even though the subroutine returns all values, I would like the script to be able to define what values it actually wants, rather than saying undef.

Replies are listed 'Best First'.
Re: Returning multiple values from subroutine
by roboticus (Chancellor) on Oct 16, 2009 at 22:34 UTC
    Moo:

    I'd suggest returning a hash ref with the values. That way, you can get only the ones you want via a hash slice, like so:

    #!/usr/bin/perl use strict; use warnings; my ($locked_status, $expire) = @{get_user_info()}{'locked_status', 'ex +pire'}; print "lock=$locked_status, exp=$expire.\n"; sub get_user_info { return { locked_status=>5, expire=>4, other_junk=>3 } }
    ...roboticus
Re: Returning multiple values from subroutine
by Marshall (Canon) on Oct 17, 2009 at 00:52 UTC
    Another way to handle is this not to change anything in the get_user_info() routine or its interface and use list slice to select which of the paramaters you want. This is a very handy technique in Perl with LOTS of applications. In this application, it allows you get to get rid of all this "undef" business.

    Below second statement says give me 1st and 6th things in list. Normally I put the left hand parms in the order that program is going to use them and adjust the slice list according, ie  [5,0], [8,1..3,0], [0,-1] are all valid slices. -1 means last thing in list, -2 would be next to last thing in list. The main limitation is that a range cannot go backwards, 0..3 is ok, but 3..0 is not.

    my ($locked,undef,undef,undef,undef,$expire)=$ssh->get_user_info($logi +nid); my ($locked,$expire) = ($ssh->get_user_info($loginid))[0,5];
    Upate: a small point, I wouldn't use -1 in slice list here, using the absolute value of 5 allows more parms to be added without affecting existing code. But here is an example where -1 works GREAT!
    my ($min,$max) = (sort @something)[0,-1];
    Note that works even if there is only one thing in @something! It is ok for the last thing in the list to be the only thing in the list.
Re: Returning multiple values from subroutine
by LanX (Saint) on Oct 16, 2009 at 22:45 UTC
    hmm .. how is the routine supposed to know the required order of return values???

    Don't think it's possible without repeating yourself ... I think it's always "Anti-DRY"!

    some ideas:

    1. passing names:

      my ($locked_status,$expire)=$ssh->get_user_info($loginid, qw(locked_status expire) );

    2. passing an anonymous hash with references

      $ssh->get_user_info( $loginid, {locked_status=>  \$locked_status, expire=> \$expire} );

    3. returning a hash-ref and slice it

      my ($locked_status,$expire)= @{ $ssh->get_user_info($loginid) }{ qw(locked_status expire) };

    4. returning a list and slice it

      my ($locked_status,$expire)= ( $ssh->get_user_info($loginid) )[$idx_locked_status,$idx_expire];

    That's untested and I'm not sure if I got the slicing right, but at least the last example is still compatible with your actual practice. But better use constants for the indices..

    Cheers Rolf

Re: Returning multiple values from subroutine
by muba (Priest) on Oct 16, 2009 at 22:29 UTC

    Edit: actually, I prefer the suggestion as given by roboticus just below.

    One approach would be to, you know... specify the things you want back - and in what order.

    my ($locked, $expire) = get_user_info( loginid => $loginid, info => [qw(locked expire)] ); # elsewhere... sub get_user_info { my %args = @_; my $loginid = $args{loginid} or die "No login id"; my @return; for my $return ( @{ $args{info} } ) { if ($return eq "locked") { my $locked = ...; push @return, $locked; } elsif ($return eq "pw_min_time") { my $pw_min_time = ...; push @return, $pw_min_time; } ... # I'm sure you get the pattern } return @return; }
Re: Returning multiple values from subroutine
by LanX (Saint) on Oct 16, 2009 at 23:05 UTC
    ... you might avoid repeating yourself if the variable name is a clear indicator of what you want by using PadWalker (EDIT: last link should work because it's CORE (!?!), ok use PadWalker). But I consider this much too dark magic.

    $ssh->get_user_info($loginid => $locked_status, $expire) ;

    ( the "=>" is just a cosmetic replacement for ",")

    But IMHO the best design is returning a hash (or hashref) and always access the values via the hash:

    my %user=$ssh->get_user_info($loginid); print $user{locked_status}; print $user{expire};

    In cases you really need these variables so often that typing the hash-syntax bothers you, you can still copy them to scalar variables.

    Cheers Rolf