current folder structure is as follows
/main/site/script.cgi
I want to be able to write to a file at
main/logs/mylog.log
How can I do this without giving the absolute path ? since This can be deployed to different servers, I wont want to have to change this every time its deployed.
To access a directory using a relative path, you need to establish first what that path will be relative to. That means you need to find out what the current working directory is for running CGI scripts.
If you add this program to your server and call it up on a browser you will see the current working directory that that server applies to all of its CGI programs.
use strict;
use warnings;
use CGI ':standard';
use Cwd 'getcwd';
print header('text/plain');
printf "Current working directory: %s\n", getcwd;
If, for instance, you find that the current working directory is /main/site then you can create the file using the path ../logs/mylog.log.
Related
I don't know how to do one thing in Perl and I feel I am doing something fundamentally wrong.
I am doing a larger project, so I split the task into different modules. I put the modules into the project directory, in the "modules/" subdirectory, and added this directory to PERL5LIB and PERLLIB.
All of these modules use some configuration, saved in external file in the main project directory - "../configure.yaml" if you look at it from the module file perspective.
But, right now, when I use module through "use", all relative paths in the module are taken as from the current directory of the script using these modules, not from the directory of the module itself. Not even when I use FindBin or anything.
How do I load a file, relative from the module path? Is that even possible / advisable?
Perl stores where modules are loaded from in the %INC hash. You can load things relative to that:
package Module::Foo;
use File::Spec;
use strict;
use warnings;
my ($volume, $directory) = File::Spec->splitpath( $INC{'Module/Foo.pm'} );
my $config_file = File::Spec->catpath( $volume, $directory, '../configure.yaml' );
%INC's keys are based on a strict translation of :: to / with .pm appended, even on
Windows, VMS, etc.
Note that the values in %INC may be relative to the current directory if you put relative directories in #INC, so be careful if you change directories between the require/use and checking %INC.
The global %INC table contains an entry for every module you have use'd or require'd, associated with the place that Perl found that module.
use YAML;
print $INC{"YAML.pm"};
>> /usr/lib/perl5/site_perl/5.8/YAML.pm
Is that more helpful?
There's a module called File::ShareDir that exists to solve this problem. You were on the right track trying FindBin, but FindBin always finds the running program, not the module that's using it. ShareDir does something quite similar to ysth's solution, except wrapped up in a nice interface.
Usage is as simple as
my $filename = File::ShareDir::module_file(__PACKAGE__,
'my/data.txt');
# and then open $filename or whatever else.
or
my $dirname = File::ShareDir::module_dir(__PACKAGE__);
# Play ball!
Change your use Module call to require Module (or require Module; Module->import(LIST)). Then use the debugger to step through the module loading process and see where Perl thinks it is loading the files from.
I am trying to FTP a directory that has subdirectories with files and images using Perl.
I tried using $ftp->rput() under Net::FTP::Recursive. But this uploads all files under local current working directory.
Is there a way to give the path of local directory and all folders and files are uploaded. Please guide.
You need to change your own working directory temporarily.
rput ( [FlattenTree => 1] [,RemoveLocalFiles => 1] )
The recursive put
method call. This will recursively send the local current working
directory and its contents to the ftp object's current working
directory.
Perl's builtin chdir can be used to change the directory. Use the Cwd module to get the current one if you need to go back.
use strict;
use warnings;
use Cwd;
use Net::FTP::Recursive;
# ...
# get the current directory
my $old_current_dir = getcwd();
# change directory to the one you want to upload
chdir('path/to/updloaddir');
# upload
$ftp->rput();
# change back
chdir($old_current_dir);
I have a perl script which is using relative file paths.
The relative paths seem to be relative to the location that the script is executed from rather than the location of the perl script. How do I make my relative paths relative to the location of the script?
For instance I have a directory structure
dataFileToRead.txt
->bin
myPerlScript.pl
->output
inside the perl script I open dataFileToRead.txt using the code
my $rawDataName = "../dataFileToRead.txt";
open INPUT, "<", $rawDataName;
If I run the perl script from the bin directory then it works fine
If I run it from the parent directory then it can't open the data file.
FindBin is the classic solution to your problem. If you write
use FindBin;
then the scalar $FindBin::Bin is the absolute path to the location of your Perl script. You can chdir there before you open the data file, or just use it in the path to the file you want to open
my $rawDataName = "$FindBin::Bin/../dataFileToRead.txt";
open my $in, "<", $rawDataName;
(By the way, it is always better to use lexical file handles on anything but a very old perl.)
To turn a relative path into an absolute one you can use Cwd :
use Cwd qw(realpath);
print "'$0' is '", realpath($0), "'\n";
Start by finding out where the script is.
Then get the directory it is in. You can use Path::Class::File's dir() method for this.
Finally you can use chdir to change the current working directory to the directory you just identified.
So, in theory:
chdir(Path::Class::File->new(abs_path($0))->dir());
Relative paths are relative to the current working directory. If you don't have any control over the working directory then you need to find a more robust way to spcecify your file paths, e.g. use absolute paths, or perhaps relative paths which are relative to some specific location within the file system.
I am using a Perl module called from a CGI scipt in IIS 6 that is bombing. The identical folder structure on an XP machine (IIS 5.1) works fantastic. If I remove the module loading command at line 9, it will print "about to load" and "ok", but When I try to run
use Language::Guess;
I receive
The specified CGI application
misbehaved by not returning a complete
set of HTTP headers.
in the browser.
The folder structure is
/cgi-bin/test.pl
/PerlModules/Language/Guess.pm
I have tried adjusting the file/folder permissions and have reviewed my IIS configuration time and again. It runs fine from the command line on the IIS machine or if I copy the module into \Perl\site\lib, but I don't have permission to load modules on the shared server this script is destined for. Am I missing something simple?
Here is test.pl
use strict;
use CGI ':standard';
print header("text/html");
use lib "..\\PerlModules\\";
print "about to load<br/>";
#bombs here
use Language::Guess;
print "ok"
The problem is the line
use lib "..\\PerlModules\\";
Change it to the full path to where the Perl modules are:
use lib "C:\\Perl\\PerlModules\\";
or whatever.
Reason is that your CGI script run from the command line in the same directory is OK, but when it is being run with an absolute path by the server from a different directory, the directory ..\\PerlModules\\ is no longer the correct location of the modules (because now .. is relative to the server's directory, not your script's). When it tries to load the module, it can't find it and prints an error message. The web server can't cope with the error message, so you get the above.
If you don't want to use the absolute path in your cgi script, investigate using use lib inside a BEGIN block with the FindBin module.
One thing which alleviates this kind of problem is the CGI::Carp module and its "fatalsToBrowser" option:
use CGI::Carp 'fatalsToBrowser';
catches mistakes & throws them out to the browser. This is for debugging only though.
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, :)