How to properly use filepath with arguments spaces in them, in Perl? - perl

Looking to open a file at '"E:\\Program Files (x86)\\Vim\\vim73\\gvim.exe -f " %s" "' and I'm getting Cannot find e:\Program.
What do I need to do to get it to work?
edit: Here's the script I am trying to get working (it's the full script as I'm not yet familar with perl at all, sorry). It's supposed to launch gvim when I make a POST request to the server via chrome's TextAid. Check out line 38

It's either single\double-quoting the path or escaping the spaces with backslashes.

system
sprintf
'"E:\\Program Files (x86)\\Vim\\vim73\\gvim.exe" -f "%s"',
$file;

I find it safer/easier to use forward slashes.
my $file = 'C:/Program Files (x86)/Adobe/Reader 10.0/Reader/AcroRd32.exe' ;
system $file ;
If you need parameters to the cmmmond
my $file = '"C:/Program Files (x86)/Adobe/Reader 10.0/Reader/AcroRd32.exe"' ;
my $data = '"C:\working_dir/music/interim/a b.pdf"' ;
system "$file $data" ;
Using single quotes makes it easier to embed double quites without having to escape things.

One way to handle this is to mess about with quoting and escapes. You need to be sure to individually quote the command and each argument to the command as a separate string.
Also, on win32 you can use real slashes instead of backslashes for directory separators. So:
system( sprintf '"E:/Program Files (x86)/Vim/vim73/gvim.exe" -f "%s"', $file );
The other, easier option is to use a list of arguments with system().
Line 38:
my #EDITOR_COMMAND = ( 'E:/Program Files (x86)/Vim/vim73/gvim.exe', '-f' );
Line 185/186:
system( #EDITOR_COMMAND, $file );
Then all the pesky quoting is taken care of for you.

our $EDITOR_CMD = "'E:\\Program Files (x86)\\Vim\\vim73\\gvim.exe' -f %s";

Related

Calling sed within perl with a variable

I would like to inplace edit ssd_config file where i need to replace the #Port to a custom port.
Before:
#Port <portnum>
ex: #Port 22
After:
Port <customport>
ex: Port 2022
Here the custom port is coming in a variable $port.
I tried the below script but does nothing.
my $prt = "Port 2022";
my $cmd = "sed -i \'s/#Port [0-9]\+/$prt/g\' sshd_config";
system($cmd);
Tried even with tick operator.
`sed -i \"s/#Port [0-9]\+/\$prt/g\" sshd_config`;
I'd suggest to do all that in Perl, once you are running a Perl program. That way one doesn't have to run external programs, not to mention the benefits of not having to mess with all the quoting and escaping if you actually need a shell (not in the shown example though).
Then we need a few more lines of code, to read the file and and edit its content and write it back out. That shouldn't matter, given all the benefits -- but there are libraries that cut out even that as well, for example the very handy Path::Tiny
use Path::Tiny;
...
path($filename)->edit_lines( sub { s/#Port [0-9]+/Port $prt/ } );
I take it that $filename and $prt have been introduced earlier in the program.
There is also a edit method, which slurps the whole file.
Anything sed can do, Perl can do.
If this is your entire Perl program:
my $prt = "Port 2022";
my $cmd = "sed -i \'s/#Port [0-9]\+/$prt/g\' sshd_config";
system($cmd);
Then you can do it all in Perl itself from the command line.
perl -i -p -e's/#Port [0-9]+/Port 2022/g' sshd_config
system("sed ....") is invoking a shell to parse the command line, which means that everything needs to be properly escaped according to the rules of the shell. This together with string escaping inside Perl makes it hard to get right. A much simpler way is to skip the shell and call sed directly and also use string concatenation to add the $prt at the right place (and don't forget to also include the "Port" string itself since you want to have it in the output):
system('sed','-i','s/#Port [0-9]+/Port ' . $prt . '/', 'sshd_config');
Alternatively one could do it in Perl directly, i.e. something like this:
open(my $fh,'<','sshd_config') or die $!;
my #lines = map { s/#Port \d+/Port $prt/ } <$fh>;
open($fh,'>','sshd_config') or die $!;
print $fh #lines;
close($fh);
This is a bit longer but does not rely on starting an external program and is thus faster. And if there is more to do than a simple replacement it is also more flexible.

Use of pipe within backtick command

I'm having an issue with some code and I'm wondering if anyone can assist.
Basically I'm trying to execute an isql query against a database and assign it to a scalar variable. The isql command makes use of the column seperator which is defined as the pipe symbol.
So I have it set-up as such:
my $command = "isql -S -U -s| -i";
my $isql_output = `$command`;
The isql command works in isolation but when issued as a backtick it stops at the pipe. I've tried concatenating the $command string using sub-strings, using single quotes and backslash escaping items such as -s\"\|\" to no avail. I've also tried using qx instead of backticks.
Unfortunately I'm currently using an older version of perl (v5.6.1) with limited scope for upgrade so I'm not sure if I can resolve this.
You have to quote the | in a way that the shell does not recognize it as a special character. Two ways:
Put the -s| into single quotes: '-s|'. Perl will leave single quotes inside double quoted strings alone and pass them to the shell unmodified.
Escape the | with two backslashes: -s\\|. Why two? The first one is seen by Perl telling it to pass the next character through unmodified. Therefore the shell sees -s\| and does something very similar: it sees the single \ and knows not to treat the next char, |, special.
The problem is that the command is being executed through a shell.
You can avoid this by passing the command and arguments in a list rather than a single string.
The backtick construct does not support that, so you would need to use the open() function instead.
I haven't tested the following code but it gives the idea:
my #command = (qw(isql -Sserver -Uuser -Ppassword -s| -w4096), '–i' . $file);
print join(' ', #command), "\n";
open(my $fh, '-|', #command)
or die "failed to run isql command: $#\n";
my #isql_output = <$fh>;
close($fh);
my $isql_output = $isql_output[0]; chomp($isql_output);
If you're working with a 15 year old version of Perl (which Oracle users tend to do) I'm not sure this will all be supported. For instance, you may need to write chop instead of chomp.
UPDATE: the problem is not the Perl version, but this construct not being supported on Windows, according to the documentation. This must be qualified: I use Perl on Cygwin and it works fine there, but I don't know whether you can use Cygwin.
Single quotes should work. Try to run test perl script:
my $cmd = "./test.sh -S -U -s '|' -i";
print `$cmd`;
With test.sh:
#!/bin/sh
echo $#
Output should be -S -U -s | -i

Perl chdir error

I am trying to change the working directory (for configure a WebShpere MQ Queue manager) using Perl in UNIX.
I have to go to the directory /var/mqm/qmgrs/Q\!MAN and I have used following code snippet:
$QueueManagerPathName = 'Q\!MAN';
chdir('/var/mqm/qmgrs/'.$QueueManagerPathName) or die "Cannot change to dir : /var/mqm/qmgrs/".$QueueManagerPathName."\n";
But it does not change the directory and dies giving
Cannot change to dir : /var/mqm/qmgrs/Q\!MAN
When i remove the variable $QueueManagerPathName its working fine and it concludes me that it would be error using "\!" part.
You don't need to escape ! in the directory name. This should work:
my $dir = '/var/mqm/qmgrs/Q!MAN';
chdir $dir or die "Can't cd to $dir: $!\n";
Single quoted strings do not interpolate backslash, so you're trying to change to a directory called /var/mqm/qmgrs/Q\!MAN
Either omit the backslash, or use a double-quoted string.
You have a backslash in your variable... I'd say that you are doing to much escaping.

problems with a perl "system" script

Im trying to write a very simple script that copies files from several remote computers to my computer. I've written the following in perl:
#Comps = ("comp1","comp2","comp3","comp4");
foreach $comp (#Comps)
{
system("copy /Y \\\\$comp\\c\$\\myfolder\\$comp*.log C:\\Program Files\\My Cont Folder\\input\\");
}
I get a syntax error and cant understand why.
Thanks!
Given the case that Perl is passing the characters to the shell, the only way that the shell will process "C:\Program Files" as one argument is to put double quotes around it. (Or sometimes just with one double quote preceding it.)
So you might need a few more backslashes (so I qq-ed it):
system( qq{cmd /c copy /Y \\\\$comp\\c\$\\myfolder\\$comp*.log "C:\\Program Files\\My Cont Folder\\input\\"} )
Looks like you've got error in the command itself, because the perl code ran fine. (Gave me 4 times "command not found" since I'm on Unix).
I would suggest using list form of system() call: system("copy", "/Y", $src, $dst); and, probably, even sprintf() to fill out $src and $dst.
Of course, you can always check what exactly gets executed by running print before system. I often hesitate and often print.
Perl and windows paths don't mix very well, why not use File::Copy instead?
use File::Copy;
copy($file1,$file2) or die "Copy failed: $!";
And then you can use forward slash in your paths instead. Much easier.

How do I run a Perl script on multiple input files with the same extension?

How do I run a Perl script on multiple input files with the same extension?
perl scriptname.pl file.aspx
I'm looking to have it run for all aspx files in the current directory
Thanks!
In your Perl file,
my #files = <*.aspx>;
for $file (#files) {
# do something.
}
The <*.aspx> is called a glob.
you can pass those files to perl with wildcard
in your script
foreach (#ARGV){
print "file: $_\n";
# open your file here...
#..do something
# close your file
}
on command line
$ perl myscript.pl *.aspx
You can use glob explicitly, to use shell parameters without depending to much on the shell behaviour.
for my $file ( map {glob($_)} #ARGV ) {
print $file, "\n";
};
You may need to control the possibility of a filename duplicate with more than one parameter expanded.
For a simple one-liner with -n or -p, you want
perl -i~ -pe 's/foo/bar/' *.aspx
The -i~ says to modify each target file in place, and leave the original as a backup with an ~ suffix added to the file name. (Omit the suffix to not leave a backup. But if you are still learning or experimenting, that's a bad idea; removing the backups when you're done is a much smaller hassle than restoring the originals from a backup if you mess something up.)
If your Perl code is too complex for a one-liner (or just useful enough to be reusable) obviously replace -e '# your code here' with scriptname.pl ... though then maybe refactor scriptname.pl so that it accepts a list of file name arguments, and simply use scriptname.pl *.aspx to run it on all *.aspx files in the current directory.
If you need to recurse a directory structure and find all files with a particular naming pattern, the find utility is useful.
find . -name '*.aspx' -exec perl -pi~ -e 's/foo/bar/' {} +
If your find does not support -exec ... + try with -exec ... \; though it will be slower and launch more processes (one per file you find instead of as few as possible to process all the files).
To only scan some directories, replace . (which names the current directory) with a space-separated list of the directories to examine, or even use find to find the directories themselves (and then perhaps explore -execdir for doing something in each directory that find selects with your complex, intricate, business-critical, maybe secret list of find option predicates).
Maybe also explore find2perl to do this directory recursion natively in Perl.
If you are on Linux machine, you could try something like this.
for i in `ls /tmp/*.aspx`; do perl scriptname.pl $i; done
For example to handle perl scriptname.pl *.aspx *.asp
In linux: The shell expands wildcards, so the perl can simply be
for (#ARGV) {
operation($_); # do something with each file
}
Windows doesn't expand wildcards so expand the wildcards in each argument in perl as follows. The for loop then processes each file in the same way as above
for (map {glob} #ARGV) {
operation($_); # do something with each file
}
For example, this will print the expanded list under Windows
print "$_\n" for(map {glob} #ARGV);
You can also pass the path where you have your aspx files and read them one by one.
#!/usr/bin/perl -w
use strict;
my $path = shift;
my #files = split/\n/, `ls *.aspx`;
foreach my $file (#files) {
do something...
}