How to determine location of perl script that was symbolic linked? - perl

I've spent quite a bit of time looking for a solution on SE, but haven't come across this issue.
I have a Perl script "SCRIPT" that can be installed in different locations, but it always reads in local files (in the same dir as the script). If the user invokes the script directly (a la ../../foo/SCRIPT) everything works as expected. The Perl script finds the local files.
But, if the user creates a symbolic link to the Perl script (ln -s my_script ../../foo/SCRIPT), and runs the sym_link, the sym_linked Perl script is looking in the current dir for the files and not the dir local to the original location of the script.
I'm trying to figure out how to determine the path that the Perl script actually resides in. Then I would prepend that onto the front of the files I want to read in. Any ideas?
I've tried calling the following--
dirname(__FILE__)
abs_path()
cwd
dirname(abs_path($0))
Cwd:cwd()
Cwd::abs_rath()
File:Basename::fileparse($0)
Cwd:realpath($0)
but they all return the path to the link, and not the actual perl script.

use FindBin qw( $RealBin );
See perldoc FindBin. FindBin is a standard module and is install with Perl.

The readlink builtin chases down the actual location of a symbolic link.
readlink EXPR
Returns the value of a symbolic link, if symbolic links are
implemented. If not, gives a fatal error. If there is some
system error, returns the undefined value and sets $! (errno).
If EXPR is omitted, uses $_.
Returns undef if the input is not a symbolic link.

Related

My Perl code does not seem to work with .lib?

I'm new to both Perl and its feature of .lib.
I made this simple subroutine and saved it as a file with an extension of .lib
sub shorterline {
print "Content-type: text/html\n\n";
}
1;
As I tried to insert the subroutine into the Perl file with an extension of .cgi below, it doesn't work somehow:
#!/usr/bin/perl
require 'mysubs.lib';
&shorterline;
print "Hello, world!";
I gave the .cgi the chmod permission, but the .cgi still doesn't work, what seem to be the problem ?
Your descriptions of what the problem is are rather unclear.
it doesn't work somehow
the .cgi still doesn't work
Without knowing what problems you're seeing, it's hard to know what the problem is. But I tried copying your code and running the program from the command line and I got this error message:
Can't locate mysubs.lib in #INC (#INC contains: ...)
So I think you are using a recent version of Perl and are running up against this change:
Removal of the current directory (".") from #INC
The perl binary includes a default set of paths in #INC. Historically it has also included the current directory (".") as the final entry, unless run with taint mode enabled (perl -T). While convenient, this has security implications: for example, where a script attempts to load an optional module when its current directory is untrusted (such as /tmp), it could load and execute code from under that directory.
Starting with v5.26, "." is always removed by default, not just under tainting. This has major implications for installing modules and executing scripts.
If this is the problem, then you can fix it by adding the script's directory to #INC as follows:
use FindBin qw( $RealBin );
use lib $RealBin;
before your call to require. If that doesn't solve your problem, perhaps you would consider sharing a little more detail about the problems that you are experiencing.

Execute a perl script within a perl script with arguments

I have met a problem when I tried to execute a perl script within my perl script. This is a small part of a larger project that I'm working on.
Below is my perl script code:
use strict;
use warnings;
use FindBin qw($Bin);
#There are more options, but I just have one here for short example
print "Please enter template file name: "
my $template = <>;
chomp($template);
#Call another perl script which take in arguments
system($^X, "$Bin/GetResults.pl", "-templatefile $template");
the "GetResults.pl" takes in multiple arguments, I just provide one here for example. Basically, if I was to use the GetResults.pl script alone, in the command line I would type:
perl GetResults.pl -templatefile template.xml
I met two problems with the system function call above. First of all, it seems to remove the dash in front of my argument when I run my perl script resulting in invalid argument error in GetResults.pl.
Then I tried this
system($^X, "$Bin/GetResults.pl", "/\-/templatefile $template");
It seems OK since it does not complain about earlier problem, but now it says it could not find the template.xml although I have that file in the same location as my perl script as well as GetResults.pl script. If I just run GetResults.pl script alone, it works fine.
I'm wondering if there is some issue with the string comparison when I use variable $template and the real file name located on my PC (I'm using Window 7).
I'm new to Perl and hope that someone could help. Thank you in advance.
Pass the arguments as an array, just as you would with any other program (a Perl script is not special; that it is a Perl script is an implementation detail):
system($^X, "$Bin/GetResults.pl", "-templatefile", "$template");
You could line everything up in an array and use that, too:
my #args = ("$Bin/GetResults.pl", "-templatefile", "$template");
system($^X, #args);
Or even add $^X to #args. Etc.

invoking perl scripts

I have perl scripts starting with #!/usr/bin/perl or #!/usr/bin/env perl
First, what does the second version mean?
Second, I use Ubuntu. All the scripts are set as executables. When I try to run a script by simply invoking it's name (e.g. ./script.pl) I get : No such file or directory. when I invoke by perl ./script.pl it runs fine.
Why?
The #!/usr/bin/env perl uses the standard POSIX tool env to work around the "problem" that UNIX doesn't support relative paths in shebang lines (AFAIK). The env tool can be used to start a program (in this case perl) after modifying environment variables. In this case, no variables are modified and env then searches the PATH for Perl and runs it. Thus a script with that particular shebang line will work even when Perl is not installed in /usr/bin but in some other path (which must be in the PATH variable).
Then, you problem with ./script.pl not working: you said it has the executable bit(s) set, like with chmod +x script.pl ? But does it also start with a shebang (#!) line ? That is, the very first two bytes must be #! and it must be followed by a file path (to perl). That is necessary to tell the kernel with which program to run this script. If you have done so, is the path correct ? You want to try the #!/usr/bin/env perl variant ;-)
Using #!/usr/bin/env perl gets around the problem of perl not necessarily being in /usr/bin on every system; it's just there to make the script more portable
On a related note, for your second problem, is there a /usr/bin/perl and/or /usr/bin/env? If not, that would explain why running the scripts directly doesn't work; the shebang isn't handled if you run the script as an argument to perl

Is there a way to load a profile inside Perl?

Is there a way to load a profile inside Perl?
Like in shell files:
. ~user/.profile
What specifically do you mean by "profile"?
If you mean "retrieve the values of the shell environmental variables that your shell set via .profile", then yes - you do it through a special %ENV hash.
If you mean "read the actual variables set in .profile" like the shell itself does, it's possible but doing it "right" requires either parsing an arbitrary shell script and scrubbing anything that's not an environmental variable assignment, OR executing ". ~/.profile; env`"` and parsing the output.
If you mean "supply a generic configuration to any Perl program that runs via a separate configuration file", you need to add code to those Perl programs to read this configuration file (there are a number of CPAN modules for reading various config files).
If you mean "supply a generic configuration to any Perl program that runs without any special code in those Perl programs to read a separate configuration file, sort of like any shell script gets the stuff from .profile thanks to shell", then the asnwer is "may be". You can leverage PERLOPT environmental variable to supply options which would load up a special module (via -I) containing the configuration that gets set via its "import()". While somewhat doable, it seems like an awful hack that I would strongly recommend against using.
Env::Sourced should do what you need.
use Env::Sourced qw(~/user/profile);
print $ENV{VARAIBLE};
If you have bash configuration values in (or other configuration set up by) a shell script and want that to take effect only for the duration of one execution of a program, you can use a subshell:
( source ~/my_bash_file.sh; perl my_perl_script.pl )
You can access shell environment variables in Perl using the %ENV hash (see the index of Perl's special variables, perldoc perlvar):
my $user = $ENV{USER};
my $home_dir = $ENV{HOME};
Example:
my_bash_file.sh:
#!/bin/bash
export HOME="/home/nowhere"
my_perl_script.pl:
#!/usr/bin/perl
print "the value of HOME is $ENV{HOME}\n";
When executed as perl my_perl_script.pl, my_perl_script.pl prints:
the value of HOME is /home/ether
When executed as ( source ~/my_bash_file.sh; perl my_perl_script.pl ), the output is:
the value of HOME is /home/nowhere
$u=`echo -n ~user`;
open F, "<$u/.profile" || die;
while(<F>)
{print}

How can I run a shell script from inside a Perl script run by cron?

Is it possible to run Perl script (vas.pl) with shell sript inside (date.sh & backlog.sh) in cron or vice versa?
Thanks.
0 19 * * * /opt/perl/bin/perl /reports/daily/scripts/vas_rpt/vasCIO.pl 2> /reports/daily/scripts/vas_rpt/vasCIO.err
Error encountered:
date.sh: not found
backlog.sh: not found
Perl script:
#!/opt/perl/bin/perl
system("sh date.sh");
open(FH,"/reports/daily/scripts/vas_rpt/date.txt");
#date = <FH>;
close FH;
open(FH,"/reports/daily/scripts/vas_rpt/$cat1.txt");
#array = <FH>;
system("sh backlog.sh $date[0] $array[0]");
close FH;
cron runs your perl script in a different working directory than your current working directory. Use the full path of your script file:
# I'm assuming your shell script reside in the same
# dir as your perl script:
system("sh /reports/daily/scripts/date.sh");
Or if your're allergic to hardcoding paths like I am you can use the FindBin package from CPAN:
use FindBin qw($Bin);
system("sh $Bin/date.sh");
If your shell script also needs to start in the correct path then it's probably better to first change your working directory:
use FindBin qw($Bin);
chdir $Bin;
system("sh date.sh");
You can do what you want as long as you are careful.
The first thing to remember with cron jobs is that you get almost no environment set.
The chances are, the current directory is / or perhaps $HOME. And the value of $PATH is minimal - your profile has not been run, for example.
So, your script didn't find 'date.sh' because it wasn't in the correct directory.
To get the data from the shell script into your program, you need to pipe it there - or arrange for the 'date.sh' to dump the data into the file successfully. Of course, Perl has built-in date and time handling, so you don't need to use the shell for it.
You also did not run with use warnings; or use strict; which would also help you. For example, $cat1 is not a defined variable.
Personally, I run a simple shell script from cron and let it deal with all the complexities; I don't use I/O redirection in the crontab file. That's partly a legacy of working on ancient systems - but it also leads to portable and reliable running of cron jobs.
It's possible. Just keep in mind that your working directory when running under cron may not be what you think it is - it's the value in your HOME environment variable, or that specified in the /etc/passwd file. Consider fully qualifying the path to your .shes.
There are a lot of things that need care in your script, and I talk about most of them in the "Secure Programming Techniques" chapter of Mastering Perl. You can also find some of it in perlsec/
Since you are taking external data and passing them to other external programs, you should use taint checking to ensure that the data are what you expect. What if someone were able to sneak something extra into those files?
When you want to pass data to external programs, use system in the list form so the shell doesn't get a chance to interpret possible meta-characters.
Instead of relying on the PATH to find the programs that you expect to run, specify their full paths explicitly to ensure you are at least running the file you think you are (and not something someone snuck into a directory that is earlier in PATH). If you were really paranoid (like taint checking is), you might also check that those files and directories had suitable permissions (e.g., not world-writeable).
Just as a bonus note, if you only want one line from a filehandle, you can use the line-input operator in scalar context:
my $date = <$fh>;
You probably want to chomp the data too to get rid of possible ending newlines. Even if you don't think a terminating newline should be there because another program created the file, someone looking at the file with a text editor might add it.
Good luck, :)