55

I need to check whether a file in a user's home directory exists so use file check:

if ( -e "~/foo.txt" ) {
   print "yes, it exists!" ;
}

Even though there is a file called foo.txt under the user's home directory, Perl always complains that there is no such file or directory. When I replace "~" with /home/jimmy (let's say the user is jimmy) then Perl give the right verdict.

Could you explain why "~" dosen't work in Perl and tell me what is Perl's way to find a user's home directory?

hippietrail
  • 15,848
  • 18
  • 99
  • 158
Haiyuan Zhang
  • 40,802
  • 41
  • 107
  • 134
  • 18
    ~ is a shell thing. That's why it doesn't work in Perl. :) – brian d foy Sep 25 '09 at 04:59
  • 1
    You could also see it like this: if this *were* supposed to work, then Perl would have to replace the tilde in *every* string by your home directory. Then you would always have to escape the tilde in every string you write. Also compare with bash: if you want tilde-expansion then your string must *not* be in quotes or otherwise bash will not expand it. So indeed you could say that Perl works exactly like Bash here: if the tilde is in quotes then it does nothing special. ;) – josch Jun 24 '18 at 06:15

6 Answers6

81

~ is a bash-ism rather than a perl-ism, which is why it's not working. Given that you seem to be on a UNIX-type system, probably the easiest solution is to use the $HOME environment variable, such as:

if ( -e $ENV{"HOME"} . "/foo.txt" ) {
    print "yes ,it exists!" ;
}

And yes, I know the user can change their $HOME environment variable but then I would assume they know what they're doing. If not, they deserve everything they get :-)

If you want to do it the right way, you can look into File::HomeDir, which is a lot more platform-savvy. You can see it in action in the following script chkfile.pl:

use File::HomeDir;
$fileSpec = File::HomeDir->my_home . "/foo.txt";
if ( -e $fileSpec ) {
    print "Yes, it exists!\n";
} else {
    print "No, it doesn't!\n";
}

and transcript:

pax$ touch ~/foo.txt ; perl chkfile.pl
Yes, it exists!

pax$ rm -rf ~/foo.txt ; perl chkfile.pl
No, it doesn't!
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 11
    There are a couple of problems with this: not all sessions have HOME set, and not everything system constructs their paths like unix. File::HomeDir will give you the right answer. – brian d foy Sep 25 '09 at 04:55
  • 8
    Never assume when you don't need to. Useful programs tend to migrate. :) – brian d foy Sep 25 '09 at 09:35
  • 1
    A _really_ late reply to this, but it might be useful for current readers -- an alternative is to use `glob('~/foo.txt')`. This has the advantage that it would properly expand things like `~user123/foo.txt` as well. I realize the OP specifically mentioned home directory, but I came across this when looking for something more generic, and thought I'd help others following the same path. – John Mar 18 '16 at 13:10
  • @John, don't forget to vote up zoul's answer here since it specifically mentions the `glob()` method. – paxdiablo Mar 18 '16 at 14:32
62

I'm not sure how everyone missed File::HomeDir. It's one of those hard tasks that sound easy because no one knows about all of the goofy exceptions you have to think about. It doesn't come with Perl, so you need to install it yourself.

Once you know the home directory, construct the path that you need with File::Spec:

 use File::HomeDir qw(home);
 use File::Spec::Functions qw(catfile);

 print "The path is ", catfile( home(), 'foo.txt' ), "\n";
Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • or use with `File::chdir` (my fav CPAN module): `... use File::chdir; { local $CWD = home; if (-e $file) { ... } }`, platform independent, block local working directory management! – Joel Berger Jun 13 '11 at 14:26
21

You can glob the tilde, glob('~/foo.txt') should work. Or you can use the File::Save::Home module that should also take care of other systems.

zoul
  • 102,279
  • 44
  • 260
  • 354
  • 6
    Why the downvote? Both of the approaches work. If there’s an issue with them, I’d like to know. – zoul Sep 25 '09 at 05:40
  • Hi I'm just a beginner but if I have a .jpg in my Win 7 home directory and I run: print glob('~/*.jpg'); it doesn't do anything. but if I run: print glob('Users/Daniel/*.jpg'); it prints: /Users/Daniel/JanesJinormousJugs.jpg – Literat Jul 01 '11 at 00:17
  • 1
    Very elegant non-portable solution. If you have a perl script that is written to run specifically in a linux environment (e.g. you shell out to bash scripts, then you're not losing anything by doing it this way, as you're already non-portable) – Michael Rusch May 16 '12 at 16:03
  • This is portable: glob works the same way on all platforms, and glob('~') returns the home directory even on Windows. – Dan R. Apr 30 '23 at 22:27
12

The home directory for a user is stored in /etc/passwd. The best way to get at the information is the getpw* functions:

#!/usr/bin/perl 

use strict;
use warnings;

print "uid:", (getpwuid 501)[7], "\n",
    "name:", (getpwnam "cowens")[7], "\n";

To address your specific problem, try this code:

if ( -e (getpwuid $>)[7] . "/foo.txt" ) {
   print "yes ,it exists!";
}
Chas. Owens
  • 64,182
  • 22
  • 135
  • 226
8

Try File::Path::Expand.

Brad Gilbert
  • 33,846
  • 11
  • 78
  • 129
phoebus
  • 14,673
  • 2
  • 33
  • 35
2

Fast forward to 2020 and there is now in perl since version 5.31.10 at its core a module called File::Glob which resolves the tilde, such as:

perl -MFile::Glob=":bsd_glob" -lE 'say File::Glob::bsd_glob( "~john" )'

would produce: /home/john

or

perl -MFile::Glob=":bsd_glob" -lE 'say File::Glob::bsd_glob( "~/some/folder" )'

would produce: /home/jack/some/folder

The File::Glob module exist since perl v5.6, but only with the glob subroutine which has been deprecated.

brian d foy
  • 129,424
  • 31
  • 207
  • 592
Jacques
  • 991
  • 1
  • 12
  • 15