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

This seemingly simple problem is stumping me. I want a concise way to test an arbitrary file "pattern" to see if the file(s) exist. The string to be tested can be in the format of a single filename, or a shell pattern such as "*.txt", "job[0-9].txt", "log.00?", etc.

I can use a -e test for a single filename, and a glob test for a pattern. What I'm looking for is a single test to use if I don't know ahead of time if the string will refer to a single file or a shell pattern.

Running a few tests with perl one-liners gives me this:

$ ls *.txt javastuff.txt modules.txt $ perl -le '$,=" "; print glob "*.txt"' javastuff.txt modules.txt $ perl -le '$,=" "; print glob "joe.txt"' joe.txt $ perl -le '$,=" "; print glob "joe.txt*"' $ perl -le 'print ((-e "joe.txt")?"yes":"no")' no $ perl -le 'print ((-e "*.txt")?"yes":"no")' no $ perl -le 'print ((-e "modules.txt")?"yes":"no")' yes $

Note the false positive for joe.txt in the glob test, and the problem with *.txt in the -e test.

I am currently using a hack that appends an asterisk (*) to the string I'm testing, and testing it with a glob command, e.g.,
if ( @files = glob $pattern.'*' ) {...
but there is an edge case where I only want "file" when "file1" and "file2" also exist, and that needs to be accounted for.

Any suggestions? Thanks.

Replies are listed 'Best First'.
•Re: Test for file(s): glob or -e?
by merlyn (Sage) on Feb 18, 2004 at 13:15 UTC
    What I'm looking for is a single test to use if I don't know ahead of time if the string will refer to a single file or a shell pattern.
    You can't know. A file named abc[d]e is perfectly legal, and yet using that string as a glob won't match that filename!

    You have to change your spec so that it is clear whether the string is a literal or a pattern. Anything else is going to get you into trouble.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      Changing the spec it is, then. I can enforce a 'files must have no shell wildcard characters in their names' policy with this.

      With that in mind, and after thinking over the problem some more, I came up with a test (update: the same test the ysth beat me to by 3 minutes) that seems to work:

      if ( grep {-e} glob $file ) {...

      In this way, if $file is a single filename that doesn't exist, it will fail on the -e test. If $file is multiple files, glob will see them all.

Re: Test for file(s): glob or -e?
by inman (Curate) on Feb 18, 2004 at 14:50 UTC
    Try testing your data for 'globbable' characters. (My list of '*' and ? is probably incomplete.)
    #! /usr/bin/perl -w $, = ', '; print $ARGV[0] =~ tr/*?// ? glob ($ARGV[0]) : ((-e $ARGV[0]) ? $ARGV[0 +] : '<No File>');

    This was tested on Win32 so $ARGV[0] is not globbed by the OS.

    Also try the following which atempts a glob followed by a single file test in case you were globbing a normal file name.

    #! /usr/bin/perl -w $, = ', '; my @files = glob('*.txt'); print @files if -e $files[0];
        My Windows background has found me out...
        $, = ', '; my @files; print ((-e $data)? $data : ((@files = glob $data) && ($data ne $files[ +0]))? @files : ()) if $data;

        Prints $data if $data is a file. Prints the globbed $data only if the glob did some work. The whole statement is protected just in case $data is empty.

Re: Test for file(s): glob or -e?
by ysth (Canon) on Feb 18, 2004 at 18:34 UTC
    I have to agree with merlyn (not that it's a struggle :) that you should specify that your input is either a filename or a glob pattern, but maybe this will help:
    # get the names if any @files = grep -e, glob $pattern;
    or
    # do any exist? if (grep -e, glob $pattern) {...

      *boggles*

      Why grep -e? Does glob make up stuff that doesn't exist in the directory now?

        By design, glob only checks for actual existence of files if you include * or ? in that part of the path:
        $ echo hi there>foo $ ls foo $ perl -wle'print for glob "{a,b,c,}fo{*,}"' afo bfo cfo foo fo
        This pattern expands to {afo,bfo,cfo,fo,afo*,bfo*,cfo*,fo*}. Only the subpatterns containing a ? or * will be restricted to what exists. In the simple case, glob "joe.txt" will always return joe.txt.