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

Hi - I'd like to do this sort of thing but the eval test fails...

#!/usr/bin/perl my %tests=( -s => "size > 0", -T => "type Text", -B => "is Binary", -r => "is readable", -z => "is Zero bytes", ); my $myfile=$0; foreach my $t (keys %tests) { if ( eval("$tests{$t} $myfile") ) { print "Passed [$t] test ($tests{$t})\n"; } else { print "Failed [$t] test ($tests{$t})\n"; } } Thanks

Replies are listed 'Best First'.
Re: File testing via list of test operators
by chrestomanci (Priest) on Dec 21, 2010 at 14:16 UTC

    You have a bug in your script on line 14. The file test operator is the key of your hash, not the value. You also need to quote your filename.

    Your code should read:

    foreach my $t (keys %tests) { if ( eval("$t '$myfile'") ) ...

    I tried out running file tests like that in the perl debugger, and the worked fine, once everything is quoted correctly.

Re: File testing via list of test operators
by Perlbotics (Archbishop) on Dec 21, 2010 at 15:06 UTC

    You can get rid of eval (potential security risk) and even define some new tests like so:

    use strict; use warnings; my %tests = ( # key, description, anonymous test function -s => { dsc => "size > 0", ftest => sub { -s $_[0] } }, -T => { dsc => "type Text", ftest => sub { -T $_[0] } }, -B => { dsc => "is Binary", ftest => sub { -B $_[0] } }, -r => { dsc => "is readable", ftest => sub { -r $_[0] } }, -z => { dsc => "is Zero bytes", ftest => sub { -z $_[0] } }, lt100 => { dsc => "small file", ftest => sub { -s $_[0] < 100 } + }, ); my $myfile = $0; print "Tests for '$myfile':\n"; foreach my $t (keys %tests) { my $result = $tests{$t}{ftest}->( $myfile ) || 0; print $result ? 'Passed' : 'Failed'; print " [$t]\t test ($tests{$t}{dsc})\t with result ($result)\n"; } __END__ Tests for 'ftest.pl': Passed [-r] test (is readable) with result (1) Passed [-T] test (type Text) with result (1) Failed [lt100] test (small file) with result (0) Failed [-z] test (is Zero bytes) with result (0) Failed [-B] test (is Binary) with result (0) Passed [-s] test (size > 0) with result (780)
    sub { -s $_[0] } is a little shorter than  sub { my $filename = shift; return -s $filename; } which is more comprehensible.
      Great answers everyone. Thanks very much. I love the final solution provided Perlbotics. Awesome. Thanks!
Re: File testing via list of test operators
by ELISHEVA (Prior) on Dec 21, 2010 at 14:17 UTC

    I think if you print out the actual string you are passing to eval the answer to your question will be obvious.

      Hi - thanks for the prompt replies. After making the changes i.e.
      foreach my $t (keys %tests) { my $cmd="$t '$myfile'"; print "running [$cmd]\n"; if ( eval{"$cmd"} )
      Perl now says ...
      running [-r './test.pl'] Passed [-r] test, ./test.pl is (is readable) running [-T './test.pl'] Passed [-T] test, ./test.pl is (type Text) running [-z './test.pl'] Passed [-z] test, ./test.pl is (is Zero bytes) running [-B './test.pl'] Passed [-B] test, ./test.pl is (is Binary) running [-s './test.pl'] Passed [-s] test, ./test.pl is (more than 0 bytes)
      So my script is a 0 byte file and also a > 0 byte file. Cool! :)
        eval{"$cmd"}
        That's true if "$cmd" is true. If you want to evaluate $cmd, then write:
        eval $cmd

        Great catch JavaFan!

        You can also get contradictory answers, in this case, both -s and -z returning false, if ./test.pl does not exist. It clearly isn't your problem here, but if you are applying this sequence of tests to arbitrary files, you might want to do the test for existence separately before passing the file through the gauntlet of your hashed up tests.

Re: File testing via list of test operators
by Anonymous Monk on Dec 21, 2010 at 14:26 UTC
    file test operators are implemented using stat and cache results into special filehandle _(underscore) so just use stat, or File::stat and forget about eval :)
      Thanks - I've looked at stat and there's no nice way to perform a set of tests. I'd have to use the "cando" method and the symbols in Fcntl. I'd like to not do that if I have to :)