Calling an application from a perl script - perl

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.

Related

How can I get perl to correctly pass a command line argument with multiple arguments and complex file paths (spaces and symbols)?

I have a small perl script which collects file paths from an excel file and passes them through the command line to perltex which then compiles a pdf based on the files and paths chosen.
My problem is that the moment I introduce more complex file paths (which is necessary based on the network setup of the final user pool) perltex fails to find the file paths, cutting them at the space.
A MWE is a follows
#!/usr/bin/perl
use strict;
use warnings;
use 5.14.2;
use Text::Template;
use Spreadsheet::Read;
use Spreadsheet::ParseXLSX;
use utf8;
use charnames qw( :full :short );
use autodie;
my $row = 5;
my $col = 15;
my $File = "C:/Users/me/Desktop/Reporting-Static/Input-test1.xlsm";
my $parser = Spreadsheet::ParseXLSX->new();
my $workbook = $parser->parse($File);
my $worksheet = $workbook->worksheet("Input");
my $cell = $worksheet->get_cell($row, $col);
my $Filename = $cell->Value();
my $texfile = "C:/Users/me/Desktop/Reporting-Static/file.tex";
# can't find this file if there are spaces in the address
system("perltex", "--latex=pdflatex", "--nosafe", "--jobname=$Filename", "$texfile");
if ( $? == -1 )
{
print "command failed: $!\n";
}
else
{
printf "command exited with value %d", $? >> 8;
}
exit;
However, the moment I change the folder name to one with spaces eg. "Reporting Static" it fails to find the tex file.
I have read several other posts regarding this on stack exchange and other websites but for whatever reason the proposed solutions do not appear to work for me. I have tried
my $texfile = "C:/Users/me/Desktop/Reporting Static/file.tex";
my $texfile = C:/Users/me/Desktop/"Reporting Static"/file.tex;
my $texfile = "\"C:/Users/me/Desktop/"Reporting Static"/file.tex\"";
my $texfile = "\"C:/Users/me/Desktop/Reporting Static/file.tex\"";
my $texfile = "C:/Users/me/Desktop/Reporting^ Static/file.tex";
my $texfile = "C:/Users/me/Desktop/Reporting\^ Static/file.tex";
As well as a few other combinations or varioations of the above, all without success. I have also tried replacing the double quote with a single quote so that perl doesn't interpolate the contents.
I have also tried manually typing all of the above into the command prompt to check whether there was a small issue with the way perl passed the commands to the command line but still no luck.
I am aware that I can use the 'dir /X ~1 c:\' command to find system name allocations that avoid spaces but the idea is that the filename and location will be dynamic and change as a funtion of department and site, so I would prefer to avoid trying to write a script which will go and find this pathname and use it to replace all locations using spaces or other special characters.
The final idea that I had is that this problem could be connected ot the way that perltex passes it's arguments yet I am unable to find any documentation (that I can follow...) on the specifics of how this particular aspect of the file functions.
So my questions are, is there something I am missing not metioned in the other answers that I have read regarding how to correctly pass these paths to perltex, is there perhaps some sort of incompatiblity in how I'm trying to go about this, is this more probabl linked to perltex as opposed to perl or cmd or is there something completely different that I am unaware of that is stopping this from working???
EDIT:
from cmd prompt perltex returns a "unable to find path X, please enter another file location". Until now I hadn't really tested retyping the path by by entering 'C:/Users/me/Desktop/"Reporting Static"/file.tex' (no quotes at the beginning) it is subsequently accepted and runs. but initially passing it this path does not work, suggesting that some internal perltex code accepts the inital path differently to being repassed the same path after an error.... not quite sure what to make of this.
EDIT:
The contents of #latexcmdline that I extracted
$VAR1 = [
'pdflatex',
'--jobname=--',
'\\makeatletter\\def\\plmac#tag{AYNNNUVKQVJGZKKPGPTH}\\def\\plmac#tofile{Perl.topl}\\def\\plmac#fromfile{Perl.frpl}\\def\\plmac#toflag{Perl.tfpl}\\def\\plmac#fromflag{Perl.ffpl}\\def\\plmac#doneflag{Perl.dfpl}\\def\\plmac#pipe{Perl.pipe}\\makeatother\\input C:/Users/me/Desktop/PERLTEST/Perl',
'Modules/RevuedeProjetDB.tex'
];
This was done by inserting
use File::Slurp;
use Data::Dumper;
write_file 'C:\Users\me\Desktop\PERLTEST\mydump.log', Dumper( \#latexcmdline );
before the exec command.
Update
I initially recommended that you should use String::ShellQuote but that module is for Linux only so I deleted my answer when I realised that your question was about the Windows system
It seems that there's also a Win32::ShellQuote which does the same thing for Windows, so I am renewing my suggestion
As I said before, the issue is that perltex itself doesn't properly handle paths containing whitespace, even if they are correctly passed as a single element of #ARGV. I believe the solution is to pass the path including enclosing quotes, although I have never been able to test this properly as I have no LaTex installation
Unfortunately, even if I pass qq{"$texfile"}, the quotes are still stripped before they reach the target program, so they must be protected in some way
You need the quote_system function from that module, which will prepare a list of strings so that they retain any quotation marks
Using a parameter of quote_system(qq{"$texfile"}) produces the correct result in my tests. It is the equivalent of passing qq{"\\"$texfile\\""} but less ugly
So your system call should be like this (with no modification to perltex.pl)
I have applied the same principle to $Filename as it may well be that this also contains whitespace
use Win32::ShellQuote 'quote_system';
system(quote_system(
'perltex',
'--latex=pdflatex',
'--nosafe',
qq{--jobname="$Filename"},
qq{"$texfile"},
));
Okay, well I have a solution of sorts
The issue, as I suspected, is that, although the path is passed as a single string to perltex.pl, the latter doesn't handle paths with spaces properly after it has received them
The temporary fix is to hack perltex.pl
Line 82 of my version of perltex.pl (there is no version number in the source) reads
$latexcmdline[$firstcmd] = "\\input $option";
If you change that to
$latexcmdline[$firstcmd] = qq{\\input "$option"};
then all should be well. However this is a solid fix only when it is distributed by the author of perltex. Meanwhile I am looking for a nicer solution from the calling side
There are two steps to solving this problem.
Work out how to get the correct arguments into an external
program.
Work out how to do that from a Perl program.
For step 1, I find a program like this to be useful.
#!/usr/bin/perl
use strict;
use warnings;
print "Received ", scalar #ARGV, " arguments:\n";
for (1 .. #ARGV) {
print "$_: $ARGV[$_ - 1]\n";
}
It just explains what arguments it receives on the command line. You can use this in place of "perltex" for testing purposes.
You'll see that if you give it an argument that contains spaces, then that is interpreted as the called program as multiple arguments. The way to get round that is to quote the argument that contains a space. And I seem to remember that Windows insists on double-quotes (for reasons that I can never remember).
So I think that you want this:
system('perltex', '--latex=pdflatex', '--nosafe', "--jobname=\"$Filename\"", "\"$texfile\"");
I've double-quoted both of the filenames. Of course, those escaped double-quote characters look really ugly, and Perl gives us qq(...) to make that look nicer.
system('perltex', '--latex=pdflatex', '--nosafe', qq(--jobname="$Filename"), qq("$texfile"));
If that's not quite right, then the program I showed earlier will make it easier to find the solution.
Update: Borodin's comment below about this making no difference to $texfile is accurate. The fact that we're passing a list to system() means that the shell isn't involved at all.

How do I make a perl script run another perl script?

I am writing a large Perl script, which needs to utilize other existing Perl scripts. The problem is the main script needs to reference many different scripts from different folders. For example the main script would be contained in:
/perl/programs/io
It may need to run a script which is stored in:
/perl/programs/tools
Note that there are other orthogonal folders besides tools so I need to be able to access any of them on the fly.
Currently this is what I got:
my $mynumber = '../tools/convert.pl bin2dec 1011';
In theory it should move back from the io directory then enter the appropriate tool directory and call the convert.pl script while passing it the parameters.
All this does is store the string in the single quotes to $myNumber.
I like to assign the output of a command to an array so I can loop through the array to find error or other messages. For example if I'm making a zip file to email to someone I want to check to see if the zip program had any errors before I continue to make and send the email.
#msgs = `zip -f myfile.zip *.pl`; # Use backticks
You can also assign the output to a scalar:
$msg = `ls -al *.pl`; # Use backticks
To run any system command or script all you have to do is use `backticks`. From observing another programer's perl code, I misread these strange quotes for 'single quotes'.
backticks are also nice because they return the text in STDOUT to your perl script so that the output can be assigned to a variable, something I have found impossible if using system("");
The similar question answer does not work with my version of perl. The line
use IPC::System::Simple qw(system capture);
throws some errors. However just using system works, like this:
my $mynumber = system($^X, "../tools/convert.pl", 'bin2dec', '1011');
I can use the above without setting equal to something to execute scripts which return no value and are only sent arguments.
This seems to be the easiest way to do what I need to and the entire programs folder can be moved anywhere and it will still work as no parent directories above programs are used.

.pm file that's loaded on every invocation of the perl interpreter?

I thought I remember reading somewhere about where perl can be configured to automatically load a certain .pm file on start up.
I know about PERL5OPT, but to my recollection, this was a specific file that would be loaded if it exists.
Is it a compile option that can be set (i.e. via Configure)?
Reading through perldoc perlrun it looks like you are looking for what is talked about in the -f option:
-f
Disable executing $Config{sitelib}/sitecustomize.pl at startup.
Perl can be built so that it by default will try to execute
$Config{sitelib}/sitecustomize.pl at startup (in a BEGIN block). This
is a hook that allows the sysadmin to customize how Perl behaves. It
can for instance be used to add entries to the #INC array to make Perl
find modules in non-standard locations.
Perl actually inserts the following code:
BEGIN {
do { local $!; -f "$Config{sitelib}/sitecustomize.pl"; }
&& do "$Config{sitelib}/sitecustomize.pl";
}
Since it is an actual do (not a require), sitecustomize.pl doesn't
need to return a true value. The code is run in package main , in its
own lexical scope. However, if the script dies, $# will not be set.
The value of $Config{sitelib} is also determined in C code and not
read from Config.pm , which is not loaded.
The code is executed very early. For example, any changes made to #INC
will show up in the output of perl -V. Of course, END blocks will be
likewise executed very late.
To determine at runtime if this capability has been compiled in your
perl, you can check the value of $Config{usesitecustomize} .
I've never done this, but it looks like if you put what you want in $Config{sitelib}/sitecustomize.pl you'll get what you are looking for.
See:
http://perldoc.perl.org/perlrun.html
http://www.nntp.perl.org/group/perl.perl5.porters/2007/10/msg129926.html
I'm confused by what you mean by "on start up". If you mean when a script / CGI / whatever is "started", then just use the module in the script:
use Data::Dumper;
Or do you mean something else?

abs_path with the home directory

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.

How do I run a Perl script from within a Perl script?

I've got a Perl script that needs to execute another Perl script. This second script can be executed directly on the command line, but I need to execute it from within my first program. I'll need to pass it a few parameters that would normally be passed in when it's run standalone (the first script runs periodically, and executes the second script under a certain set of system conditions).
Preliminary Google searches suggest using backticks or a system() call. Are there any other ways to run it? (I'm guessing yes, since it's Perl we're talking about :P ) Which method is preferred if I need to capture output from the invoked program (and, if possible, pipe that output as it executes to stdout as though the second program were invoked directly)?
(Edit: oh, now SO suggests some related questions. This one is close, but not exactly the same as what I'm asking. The second program will likely take an hour or more to run (lots of I/O), so I'm not sure a one-off invocation is the right fit for this.)
You can just do it.
{
local #ARGV = qw<param1 param2 param3>;
do '/home/buddy/myscript.pl';
}
Prevents the overhead of loading in another copy of perl.
The location of your current perl interpreter can be found in the special variable $^X. This is important if perl is not in your path, or if you have multiple perl versions available but which to make sure you're using the same one across the board.
When executing external commands, including other Perl programs, determining if they actually ran can be quite difficult. Inspecting $? can leave lasting mental scars, so I prefer to use IPC::System::Simple (available from the CPAN):
use strict;
use warnings;
use IPC::System::Simple qw(system capture);
# Run a command, wait until it finishes, and make sure it works.
# Output from this program goes directly to STDOUT, and it can take input
# from your STDIN if required.
system($^X, "yourscript.pl", #ARGS);
# Run a command, wait until it finishes, and make sure it works.
# The output of this command is captured into $results.
my $results = capture($^X, "yourscript.pl", #ARGS);
In both of the above examples any arguments you wish to pass to your external program go into #ARGS. The shell is also avoided in both of the above examples, which gives you a small speed advantage, and avoids any unwanted interactions involving shell meta-characters. The above code also expects your second program to return a zero exit value to indicate success; if that's not the case, you can specify an additional first argument of allowable exit values:
# Both of these commands allow an exit value of 0, 1 or 2 to be considered
# a successful execution of the command.
system( [0,1,2], $^X, "yourscript.pl", #ARGS );
# OR
capture( [0,1,2, $^X, "yourscript.pl", #ARGS );
If you have a long-running process and you want to process its data while it's being generated, then you're probably going to need a piped open, or one of the more heavyweight IPC modules from the CPAN.
Having said all that, any time you need to be calling another Perl program from Perl, you may wish to consider if using a module would be a better choice. Starting another program carries quite a few overheads, both in terms of start-up costs, and I/O costs for moving data between processes. It also significantly increases the difficulty of error handling. If you can turn your external program into a module, you may find it simplifies your overall design.
All the best,
Paul
I can think of a few ways to do this. You already mentioned the first two, so I won't go into detail on them.
backticks: $retVal = `perl somePerlScript.pl`;
system() call
eval
The eval can be accomplished by slurping the other file into a string (or a list of strings), then 'eval'ing the strings. Heres a sample:
#!/usr/bin/perl
open PERLFILE, "<somePerlScript.pl";
undef $/; # this allows me to slurp the file, ignoring newlines
my $program = <PERLFILE>;
eval $program;
4 . do: do 'somePerlScript.pl'
You already got good answers to your question, but there's always the posibility to take a different point of view: maybe you should consider refactoring the script that you want to run from the first script. Turn the functionality into a module. Use the module from the first and from the second script.
If you need to asynchronously call your external script -you just want to launch it and not wait for it to finish-, then :
# On Unix systems, either of these will execute and just carry-on
# You can't collect output that way
`myscript.pl &`;
system ('myscript.pl &');
# On Windows systems the equivalent would be
`start myscript.pl`;
system ('start myscript.pl');
# If you just want to execute another script and terminate the current one
exec ('myscript.pl');
Use backticks if you need to capture the output of the command.
Use system if you do not need to capture the output of the command.
TMTOWTDI: so there are other ways too, but those are the two easiest and most likely.
See the perlipc documentation for several options for interprocess communication.
If your first script merely sets up the environment for the second script, you may be looking for exec.
#!/usr/bin/perl
use strict;
open(OUTPUT, "date|") or die "Failed to create process: $!\n";
while (<OUTPUT>)
{
print;
}
close(OUTPUT);
print "Process exited with value " . ($? >> 8) . "\n";
This will start the process date and pipe the output of the command to the OUTPUT filehandle which you can process a line at a time. When the command is finished you can close the output filehandle and retrieve the return value of the process. Replace date with whatever you want.
I wanted to do something like this to offload non-subroutines into an external file to make editing easier. I actually made this into a subroutine. The advantage of this way is that those "my" variables in the external file get declared in the main namespace. If you use 'do' they apparently don't migrate to the main namespace. Note the presentation below doesn't include error handling
sub getcode($) {
my #list;
my $filename = shift;
open (INFILE, "< $filename");
#list = <INFILE>;
close (INFILE);
return \#list;
}
# and to use it:
my $codelist = [];
$codelist = getcode('sourcefile.pl');
eval join ("", #$codelist);