1

I would like to check if a file name has any directory information within it, preferably without using a system dependent hack like index($file_name,'/')!=-1. I know of the File::Spec module, but can't seem to find a way to use that module to make this determination. Perhaps another module would work better. Here are some examples of file names:

# This does not have directory info, so the test should fail (i.e., return false)
my $file_name1='abc.txt';

# These do have directory info, so the test should succeed (i.e., return true)
my $file_name2='dir/abc.txt';
my $file_name3='/home/me/abc.txt';
my $file_name4='~me/abc.txt';
RobEarl
  • 7,862
  • 6
  • 35
  • 50
Michael Goldshteyn
  • 71,784
  • 24
  • 131
  • 181
  • So you want a portable way to do `index($file_name,'/')!=-1`? – mob Feb 28 '14 at 16:51
  • Those examples you given in your question are path, not file name, except `$file_name1`, which indeed is a file name. – Lee Duhem Feb 28 '14 at 17:34
  • @mob: Unless your world has only Unix-based systems you will find that there are many different ways of fully-qualifying a file on a filing system. The volume, path, and name of files can be delimited in many different ways, and that is why `File::Spec` exists. – Borodin Feb 28 '14 at 17:38
  • 1
    Should `~me` return true or false? It is a directory but contains no directory separators. – ThisSuitIsBlackNot Feb 28 '14 at 17:42
  • @ThisSuit, reread my question. Specifically, I would like to check if a **file name** has any directory information. Does `~me` look like a file name or a home directory? – Michael Goldshteyn Mar 05 '14 at 14:29
  • @MichaelGoldshteyn No need to be snippy. I was trying to cover edge cases to make sure you got the best answer possible and lost sight of the fact that you're only passing file names. – ThisSuitIsBlackNot Mar 05 '14 at 15:14

4 Answers4

3

splitdir will return a list. Evaluated in scalar context, it returns the number of items in the list. If there is more than 1 item in the list, then you know there is a directory separator.

use warnings;
use strict;
use File::Spec qw();

while (<DATA>) {
    chomp;
    if (File::Spec->splitdir($_) > 1) {
        print 'PASS';
    }
    else {
        print 'FAIL';
    }
    print ": $_\n";
}

__DATA__
abc.txt
dir/abc.txt
/home/me/abc.txt
~me/abc.txt

Prints:

FAIL: abc.txt
PASS: dir/abc.txt
PASS: /home/me/abc.txt
PASS: ~me/abc.txt
toolic
  • 57,801
  • 17
  • 75
  • 117
1

EXAMPLE

($volume,$directories,$file) =
                       File::Spec->splitpath( $path );

splitpath Splits a path in to volume, directory, and filename portions. On systems with no concept of volume, returns '' for volume.

rnrneverdies
  • 15,243
  • 9
  • 65
  • 95
1

You must use File::Spec to get a properly portable result.

If the complexity bothers you then you should write your own library that wraps the File::Spec functions.

It could look like this

File PathLib.pm

package PathLib;
use strict;
use warnings;

require File::Spec;
require Exporter;

our @ISA       = qw(Exporter);
our @EXPORT_OK = qw(has_path);

sub has_path {
  my ($volume, $path, $name) = File::Spec->splitpath($_[0]);
  return ($path or $volume)
}

and you could use it like this. (I am currently working on a Windows laptop and cannot test a Unix version until I get to a desk, but I am certain that this will work for you.)

use strict;
use warnings;

use PathLib qw(has_path);

my @paths = qw{
  C:\aa\bb\cc.txt
  E:ee.txt
  cc.txt
  ee\
};

for (@paths) {
  printf "%6s - %s\n", has_path($_) ? 'true' : 'false', $_;
}

output

  true - C:\aa\bb\cc.txt
  true - E:ee.txt
 false - cc.txt
  true - ee\
ThisSuitIsBlackNot
  • 23,492
  • 9
  • 63
  • 110
Borodin
  • 126,100
  • 9
  • 70
  • 144
  • Your function returns false for `foo/`. Also, your output is reversed (`cc.txt` should print `no`). – ThisSuitIsBlackNot Feb 28 '14 at 18:13
  • The reversal of the logic is a result of multiple changes of thought while I was writing the answer and has been fixed. The `true` result for `ee\` is correct, because the path *does* contain directory information. – Borodin Feb 28 '14 at 20:05
  • @ThisSuitIsBlackNot: My apologies: I realise I had published an old version of my module. – Borodin Feb 28 '14 at 20:09
0

In Perl, the / is always reserved as a directory separator (except for the old Mac OS System [6-9] which insisted that it be :). However, in other operating systems, other symbols could be used too. For example, on Windows machines, both of these refer to the same directory:

$file = 'C:\this\that\foo.txt';
$file = "C:/this/that/foo.txt";

So, you should always suspect that / is a directory specifier unless you happen to be on an old Mac. And, you should also figure out what the current Path separator may be on that system and look for that one too.

I was hoping that File::Spec or File::Path might have some function or method that reveals the name of the preferred directory separator, but there's none documented.

Looking through the code of the various File::Spec sub-modules, I see that the actual file separator is hard coded, and the functions are more complex than I originally thought. No special hidden function. Drat.

However, the good news is that File::Spec is a standard module, so there's no reason to fear it. It was even included in Perl 5.8.8. You don't have to worry that it might not be installed.

I did a simple test:

#! /usr/bin/env perl
#
use strict;
use warnings;
use feature qw(say);

use File::Spec;

while ( my $file = <DATA> ) {
    chomp $file;
    my ( $volume, $directory, $file )  = File::Spec->splitpath( $file );
    if ( $directory ) {
        say qq('$volume' '$directory' '$file' contains directory stuff);
    }
    else {
        say qq('$volume' '$directory' '$file' is pure file);
    }
}


__DATA__
Foo/Bar.txt
/Bar.txt
Bar.txt/
Foo_Bar.txt

The results were:

'' 'Foo/' 'Bar.txt' contains directory stuff
'' '/' 'Bar.txt' contains directory stuff
'' 'Bar.txt/' '' contains directory stuff
'' '' 'Foo_Bar.txt' is pure file

It appears that anytime a directory separator appears, the string is considered a file. I guess this makes sense.

This is one of those tasks that end up being way more complex than you realize (such as determining whether or not a string is a valid email address or a number). Your best bet is to use File::Spec->split and make sure there's no volume or directory set.

David W.
  • 105,218
  • 39
  • 216
  • 337