abs_path with the home directory - perl

I created a perl script that uses abs_path but it doesn't correctly handle the home directory (represented by '~')
EG if I try to call abs_path("~/mystuff");
it returns undef
Can I make abs_path correctly handle the home directory? And if not, is there an alternative I can use?

#!/usr/bin/perl
use File::HomeDir;
print File::HomeDir->my_home;

The reason that abs_path doesn't handle it correctly is that "~" is a shell construct. Perl doesn't know anything about what "~" means - it literally treats it as "a directory named 'tilde'(~) under current working directory".
Any program to which "~" is supplied as a parameter would actually get a real directory path from shell instead.
To be able to use home directories from Perl, on Unix you can use $ENV{HOME} instead of "~" for your own home directory; or for other users use (getpwnam($user))[7]; there's no clean cross-platform way to do either.
A second approach is to use shell for dirty work in a system call:
my $expanded_home_dir = `cd ~/mydir/; pwd`;
UPDATE:
First, a very good recipe for outright replacement of tilde-home-strings is in "Perl Cookbook" (2d ed), ch. 7.3 "Expanding tildes in filenames".
Second, as daxim mentioned in comments, since Perl 5.6, CORE::glob() is actually automatically replaced with File::Glob::bsd_glob() which supports POSIX's GLOB_TILDE flag and therefore can expand tildes for you.
Interestingly enough, both bsd_glob (in C code, as per Perlmonks), and File::HomeDir mentioned wisely by Andrew Clark, use precisely the same logic underneath as the Cookbook's recipe 7.3 ($ENV{HOME}||$ENV{LOGDIR}||(getpwnam($<))[7]) for Unix environments.

Related

how to add slash separator in perl

I have the following command
how could I add "\" (path) between v1 and bin in perl to say : c:\path\bin
as v1 = c:\path
system(dirname($v1) . $bin . " " . $trinp);
could someone recommend tutorial for perl?
Thanks
If you are trying to create a path, using File::Spec is a good idea. It is a core module, so it will already be on your system.
use strict;
use warnings;
use File::Spec;
my #path = qw(C: path bin);
my $path = File::Spec->catdir(#path);
print $path;
Output in Windows:
C:\path\bin
If you wish to emulate another OS, you can select the appropriate module for it, e.g.
use File::Spec::Win32;
use File::Spec::Unix;
As for myself, if I use the Unix version, I get the output:
C:/path/bin
(Note for forward slashes)
You have asked a question that has multiple answers. To just put a backslash in to form a Windows path, you need to escape the backslash thus, and this will solve your direct problem:
system($v1 . "\\" . $bin . " " . $trinp);
However, there are a few refinements. Firstly, you can use Perl's interpolation to make it easier to read:
system("$v1\\$bin $trinp");
You can also use Unix-style forward slashes, as Windows understands these, and prevents so-called "backslashitis":
system("$v1/$bin $trinp");
A better way however is to use Path::Class to generate paths on any operating system.
Further, there is a potential security problem in your code that can be caused if $trinp (or any of the other variables) contains shell metacharacters such as | to pipe into another command. If you provide a list of arguments to the system() function, it will pass those directly to the command instead of the shell. You will probably also want to check if the command was successful, and do something useful (e.g. die()ing) if it failed:
system("$vi/$bin", $trinp) == 0
or die "Couldn't run $vi/$bin: $!";
I'll leave it as an exercise for you to convert my final example to use Path::Class.
[Edited to correct misuse use of system(), which returns the status of the command that was run which is zero for success. I confused it with more common other Perl functions which return nonzero for success.]
There are a great many very dubious Perl tutorials out there on the internet. The Perl Tutorial Hub is a good place to find better quality ones.

Calling an application from a perl script

I would like to call an application from a perl script using the 'system' command. However, the application is placed in a separate directory. How can I call the application from that directory in my perl script. Can I use "require"?
You can just change directory if you want to run the application in the directory where the application resides. See the chdir command in perlfunc.
Otherwise, just add the path like this:
system('/path/to/the/application');
No, require doesn't work -- require is used to pull in other perl files, once. If, of course, the file you'd like to call to is a perl program ... well, it's probably better to factor out the parts you'd like to be shared between the two programs, rather than requireing it, or to simply treat it as a black box anyway if you don't want to do that.
To call an executable which is neither in the search path -- simply being in the same directory is not sufficient! -- supply the full path to, e.g. system, exec, &c.. For system, the preferred forms would be:
system { '/path/to/executable' } 'argument 0 (i.e. $0) supplied to program', 'argument 1 (equivalent to $ARGV[0])', ...;
or
# $path = ...
system $path 'arg 0', 'arg 1', ...;
or
# $path = ...
system $path #args;
Of course, it is good form to check the result of each system call in case of errors.
Note the use of indirect-object (i.e. with an argument passed without comma, like the fh for a print or such) system in every case! Unless you know very well what you're doing and why, it's hard to recommend risk using the one-argument form of system, because it subjects your input to the whims of shell preprocessing.
Consult perldoc -fsystem and perldoc -fexec for more detail.

Java's File.pathSeparator in Perl

Is there a module or constant somewhere in Perl that gives the equivalent of Java's File.pathSeparatorChar? I've been looking everywhere but the name easily brings up things like / and \, used to separate elements in a single path. I want the thing you use to separate values in an environment variable containing paths (: in Unix, ; in Windows, and I have no idea if there are others on other platforms).
I believe you are looking for $Config{path_sep}:
use Config;
print "path separator for $^O is '$Config{path_sep}'\n";
path separator for cygwin is ':'
(I found this be grepping for split.*PATH in the perl source code)
If you want to actually do anything with the system PATH variable, then you probably want the Env::Path module, but that indeed gets the value for the separator from $Config::Config{path_sep}.
Note that it is cleaner to use the Config module as
use Config ();
and then access the hash using the fully-qualified %Config::Config, as otherwise the module silently imports %Config into your current namespace.

How can I "use lib" the appropriate directory depending on installation location?

I have an object-oriented web-app that is installed in multiple locations on my server. Once for "live", once for "beta", etc. Being object-oriented, it consists of many perl modules. In the main module, I must "use lib" the appropriate directory for all of the custom perl modules for that instance of the app.
This is no big deal, I have a BEGIN block that checks the location of the main program and sets the library directory appropriately. However I also have a lot of utility, command line programs that need to do the same thing. I don't want to cut and paste this code everywhere.
What is the best way to share this code snippet amongst the various programs that need it?
I can't "use" it because the libary path isn't set up yet. Maybe "do" or "require" would the be the right answer, but both of those will search #INC, which is inappropriate.
Maybe something like eval `cat GetLib.pl`; would be appropriate but it seems kind of clunky and fragile.
Here is the BEGIN block that I currently use:
BEGIN {
use FindBin qw ($Bin);
require lib;
if ($Bin =~ /^\/home\/w\/myapp_live/) {
lib->import('/home/w/myapp_live/lib');
print STDERR "live site\n";
}
if ($Bin =~ /^\/home\/w\/myapp_beta/) {
lib->import('/home/w/myapp_beta/lib');
print STDERR "beta site\n";
}
if ($Bin =~ /^\/home\/w\/myapp_test/) {
lib->import('/home/w/myapp_test/lib');
print STDERR "testing site\n";
}
}
Thank you!
FindBin::libs is excellent for that. I've used it for a while in a large system with no problems at all.
The default invocation looks like it'll work for you, simply:
use FindBin::libs;
This will search for all the ./lib dirs in all the parent directories of the current file's dir and use lib them. So, for example, if your script lives in /home/w/myapp_live/scripts/defurblise_widgets.pl (and use()es FindBin::libs), it will look for:
/home/w/myapp_live/scripts/lib
/home/w/myapp_live/lib
/home/w/lib
/home/lib
/lib # (presumably!)
Any that it finds with be added to you #INC with use lib.
But, if that's not quite what you need, it's a very flexible module. I'd be surprised if you can't find a way to make it do what you want.
If you're running a program from the command line, don't programmatically set the lib: just pass it in as an argument, e.g.: perl -I/location/of/my/lib myprog.pl.
For your web app, why don't you make your library relative to the location of the script itself? Then just install it on each machine where the libraries live.
use FindBin;
use File::Spec::Functions;
use Cwd qw(abs_path getcwd);
BEGIN {
my $curdir = getcwd;
my $selfdir = $FindBin::Bin;
my $libdir = abs_path(catdir($selfdir, 'lib'));
chdir $libdir or die "can't chdir to $libdir: $#";
use lib $libdir;
}
Of course, the easiest option of all is to not use different lib directories. Why can't you be consistent across all your environments?
Edit. Re your comment "The reason I have to use different lib directories is because the code running in the live site is different than the code running on the beta site... that's the point of having a beta site." -- why don't you handle this at the level of the installer, rather than making the code itself have to know whether it's live vs. beta? e.g. store your code in different directories in your source tree as you do now, but only install the relevant code to each box. After all, that's exactly what a good revision control system would do for you -- you only check out one branch at a time, and you should only be installing one version of code at a time (as brian d foy alluded to).
I use the following in many of my scripts:
use strict;
use warnings;
use 5.008;
use FindBin;
use lib $FindBin::Bin;
That last line could be modified as such:
use FindBin;
use lib "$FindBin::Bin/lib";
The different environments could have different settings for the environment variable $PERL5LIB.
The code you've shown looks reasonable. You could install one copy of that code into a system-wide location, and then the code to invoke it would boil down to
require '/path/to/findlib.pl';
findlib->import;
The form of require that takes a filename doesn't search #INC.
As an alternative if you wanted to change lots of things around you could look into deploying the app in a way that would be more friendly to local::lib usage.

What Perl module(s) do I use to obtain an absolute path (including filename) from a relative one on Windows?

I can only imagine I'm not searching correctly; this seems like an obvious question to be asked here. My apologies if this is a duplicate.
I'm writing a Perl program that will take a filename as a command-line argument. I need to convert the filename (or the filename with a relative path attached) to an absolute path (specifically to work with Win32::OLE).
I tried using Cwd's 'abs_path', and that almost does what I want, but it returns it using a Unix-style path instead of a Win32 one.
Is there a module that will convert the path, or perhaps a better module to use in the first place?
I use rel2abs from File::Spec. You have to be careful though: that might call getdcwd from Cwd, and it will assume that you want the current working directory for the current drive. If the file is on some other drive, you'll have to fix that up yourself or supply the second argument to set the base path.
use File::Spec::Functions qw(rel2abs);
print rel2abs($ARGV[0]), "\n";
my($foo) = abs_path($some_file);
$foo =~ s{/}{\\}g;
print "FOO: $foo\n";
I use Cwd's abs_path and then use a regex to convert the slashes when I really need it done. But I've found that for most uses, Unix-style slashes work just fine. It's only for the occasional "pass a filename to that annoyingly limited program" that I end up needing to convert the slashes.
use Cwd 'abs_path';
my $path = abs_path($rel_path);
# and only if necessary...
$path =~ s'[/\\]+'\\'g; # use Windows-style slashes
$path =~ s'^\\'\\\\'; # handle network path
But then.. I use a lot of network paths, with or without a mapped drive reference. Your mileage may vary.