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

Greetings all I have a problem to pass an operator as an argumnt to a sub, like this:
&checkDir('/usr/tmp', '-w'); #or... &checkDir('/usr/tmp', '-r') sub checkDir { my ($dir, $perm) = @_; ### Check if output dir exists if (! defined $path) { LOG ("The path where to save the output is not defined using temp +instead...", 1); $path = '/var/tmp/'; } if (! -d $path) { die LOG ("$path is not a valid directory...", 0); } if (! $perm $path) { die LOG ("You do not have $perm to $path directory...", 0); } LOG ("Using $path as output directory...", 2); }
I have tried to deref the $perm var, but it does not help either. Is it possible at all to pass an operator as an argument?? Cheers and thanks Pär

Replies are listed 'Best First'.
Re: how to pass operators as arguments to a sub
by borisz (Canon) on May 03, 2006 at 09:45 UTC
    Here is a template without eval.
    use warnings; use strict; my %h = ( -r => sub { -r shift }, -w => sub { -w shift }, ); my $testDir = '/tmp/'; checkDir( $testDir, '-w' ); checkDir( $testDir, '-r' ); sub checkDir { my ( $dir, $perm ) = @_; die '???' unless exists $h{$perm}; if ( $h{$perm}($dir) ) { print "Can $perm $dir\n"; } else { print "Can not $perm $dir\n"; } }
    Boris
Re: how to pass operators as arguments to a sub
by davido (Cardinal) on May 03, 2006 at 07:03 UTC

    Probably the safest way is to use a hash dispatch table. There are only a small number of possible hyphen-operators anyway. Another alternative would be to use the "quote" version of eval, but that just exposes you to too much potential for an eternity in outer darkness... so stick with a dispatch table.


    Dave

Re: how to pass operators as arguments to a sub
by GrandFather (Saint) on May 03, 2006 at 07:02 UTC

    The following test code works fine. Alter it to demonstrate your problem:

    use warnings; use strict; checkDir('/usr/tmp', '-w'); #or... checkDir('/usr/tmp', '-r'); sub checkDir { my ($dir, $perm) = @_; print "Dir: $dir, Perm: $perm\n"; }

    Prints:

    Dir: /usr/tmp, Perm: -w Dir: /usr/tmp, Perm: -r

    Bah, read the code!

    Ok, better answer - to the actual question:

    use warnings; use strict; my $testDir = '.'; checkDir($testDir, '-w'); checkDir($testDir, '-r'); sub checkDir { my ($dir, $perm) = @_; if (eval "$perm '$dir'") { print "Can $perm $dir\n"; } else { print "Can $perm $dir\n"; } }

    Prints:

    Can -w . Can -r .

    This may be a bad idea if the call passes in user supplied text is is not from a trusted place!


    DWIM is Perl's answer to Gödel
      Thanks, Just what I needed. Completely forgot the eval command. And no, it is not unsafe, the argument comes from my own program, I just wanted to make it general. Cheers Pär
Re: how to pass operators as arguments to a sub
by jonadab (Parson) on May 03, 2006 at 14:21 UTC

    Rather than passing an operator per se, I'd pass a code reference that uses the operator...

    checkDir('/usr/tmp', sub { -w shift }); # need write permission. checkDir('/usr/tmp', sub { -r shift }); # need read permission. checkDir('/usr/tmp', sub { my $x = shift; -r $x and -w $x; }); # need +both. checkDir('/usr/tmp', sub { my $x = shift; not $x =~ /\.\./; }); # safe +ty check. sub checkDir { my ($path, $perm) = @_; ### Check if output dir exists if (! defined $path) { LOG ("Output path not defined; using temp instead...", 1); $path = '/var/tmp/'; } if (! -d $path) { die LOG ("$path is not a valid directory...", 0); } if (! $perm->($path)) { die LOG ("Test failed on $path, not able to use it.", 0); } LOG ("Using $path as output directory...", 2); }

    Sanity? Oh, yeah, I've got all kinds of sanity. In fact, I've developed whole new kinds of sanity. Why, I've got so much sanity it's driving me crazy.

      While I like this idea, a couple of minor comments. First, there's an awful lot of shifting going on. For things like this, the topicaliser, $_, is very useful. So you'd be able to set things up as:

      checkdir('/usr/tmp', sub { -w $_ }); checkdir('/usr/tmp', sub { -r $_ }); checkdir('/usr/tmp', sub { -r $_ and -w $_ });
      At this point, I want to point out my second minor comment. This is what _ is for.
      # now it's: checkdir('/usr/tmp', sub { -r $_ and -w _ }); checkdir('/usr/tmp', sub { !/\.\./ });
      Again, my next minor comment: I'm not sure what this is supposed to be checking. I'm guessing you're looking for a directory named ".." somewhere in there. However, I'm guessing that "/usr/blah..foo" should be permitted? Regexes are powerful, but with power comes great respon... er, complexity ;-) Rather than use a regex, I would actually suggest doing exactly what a human would do: split the path elements up, and then look that none of them are equal to "..". If that's what you really want. (On unix, I claim that this is almost never what you want, due to symlinks.)
      use List::MoreUtils qw/none/; use File::Spec; checkdir('/usr/tmp', sub { none { $_ eq '..' } File::Spec->splitdir($_) });
      With those minor comments, here's the minor change to checkDir:
      sub checkDir { my ($path, $perm) = @_; ### Check if output dir exists if (! defined $path) { LOG ("Output path not defined; using temp instead...", 1); $path = '/var/tmp/'; } if (! -d $path) { die LOG ("$path is not a valid directory...", 0); } if (! do { local $_ = $path; $perm->() }) { die LOG ("Test failed on $path, not able to use it.", 0); } LOG ("Using $path as output directory...", 2); }
      A bit more convoluted internally, but I think a much more perlish exterior.