system () rsync with variable path with spaces - perl

I need to pass a variable path with spaces to Perl's system rsync command. My script contains these variables:
$mht_user = "username";
$mht_share = "/mypath/with some space/01. continue/another space/";
$mht_dstpath = $mht_user.'#'.$mht_host.":".$mht_share."";
I need to pass the destination path to the rsync command
system ("/usr/bin/rsync -acz -H --delete /my source path ".$mht_dstpath.");
rsync does not like the path with that spaces, so I tried to write the path in this mode:
$mht_share = "/mypath/with\ \some\ \space/01.\ \continue/another\ \space/";
rsync accepts it, but it breaks the path into:
/mypath/with
How can I pass the path with spaces to rsync?

Understanding the Issue
This is ultimately a shell issue. Let's rsync out of the loop to illustrate the problem
Here is a simple script, printargs.pl, that lists its arguments. We can use it to show what actually gets passed to a program when you run system
#!/usr/bin/perl
use strict;
use warnings;
print "[$_]\n" for #ARGV;
Now call it with some of the parameter list you are using
use strict;
use warnings;
my $mht_user = "username";
my $mht_host = "host";
my $mht_share = "/mypath/with some space/01. continue/another space/";
my $mht_dstpath = $mht_user.'#'.$mht_host.":".$mht_share."";
system ("perl /tmp/echoargs.pl -acz -H --delete /my source path $mht_dstpath.");
output is
[-acz]
[-H]
[--delete]
[/my]
[source]
[path]
[username#host:/mypath/with]
[some]
[space/01.]
[continue/another]
[space/.]
Note that the shell has split /my source path username#host:/mypath/with some space/01. continue/another space/. on the embedded spaces
Option1: Use Shell quoting
You can use the single-quotes to enclose the string with embedded spaces, like this
use strict;
use warnings;
my $mht_user = "username";
my $mht_host = "host";
my $mht_share = "/mypath/with some space/01. continue/another space/";
my $mht_dstpath = $mht_user.'#'.$mht_host.":".$mht_share."";
# Use single-quotes to enclose the string with embedded spaces
system ("perl /tmp/echoargs.pl -acz -H --delete '/my source path $mht_dstpath.'");
output is
[-acz]
[-H]
[--delete]
[/my source path username#host:/mypath/with some space/01. continue/another space/.]
Option 2: avoid using a shell
This if from the system documentation
If there is more than one argument in LIST,
or if LIST is an array with more than one value, starts the
program given by the first element of the list with arguments
given by the rest of the list.
The meaning of that slightly confusing sentence is -- if you pass the program name and its parameters to system as a Perl list, you will bypass the shell.
use strict;
use warnings;
my $mht_user = "username";
my $mht_host = "host";
my $mht_share = "/mypath/with some space/01. continue/another space/";
my $mht_dstpath = $mht_user.'#'.$mht_host.":".$mht_share."";
# pass parameters individually to bypass the shell
system ("perl", "/tmp/echoargs.pl", "-acz", "-H", "--delete", "/my source path $mht_dstpath.");
outputs
[-acz]
[-H]
[--delete]
[/my source path username#host:/mypath/with some space/01. continue/another space/.]

Related

How can I pass arguments for a subroutine from the command line

First off, my background is primarily in Python and I am relatively new at using Perl.
I am using tcsh for passing options into my .pl file.
In my code I have:
if( scalar #ARGV != 1)
{
help();
exit;
}
# Loading configuration file and parameters
our %configuration = load_configuration($ARGV[0]);
# all the scripts must be run for every configuration
my #allConfs = ("original");
sub Collimator
{
my $z = -25.0;
my %detector = init_det();
$detector{"pos"} = "0*cm 0.0*cm $z*cm"
}
foreach my $conf ( #allConfs )
{
$configuration{"variation"} = $conf ;
#other geometries ....
Collimator();
}
I want to add something that allows me to change the parameters of the subroutine in the .pl file from the command line. Currently, to generate the geometry I pass the following command into the tcsh CLI: perl file.pl config.dat. What I want is to be able to pass in something like this: perl file.pl config.dat -20.0.
I'm thinking that I need to add something to the effect of:
if($ARGV[1]){
$z = ARGV[1]}
However, I am not sure how to properly implement this. Is this something that I would specify within the subroutine itself or outside of it with the loading of the configuration file?
Use a library to handle command-line arguments, and Getopt::Long is excellent
use warnings;
use strict;
use Getopt::Long;
my ($config_file, #AllConfs);
my ($live, $dry_run) = (1, 0);
my $z;
GetOptions(
'config-file=s' => \$config_file,
'options-conf=s' => \#AllConfs,
'geom|g=f' => \$z,
'live!' => \$live,
'dry-run' => sub { $dry_run = 1; $live = 0 },
# etc
);
# Loading configuration file and parameters
# (Does it ===really=== need to be "our" variable?)
our %configuration = load_configuration($config_file);
my #data_files = #ARGV; # unnamed arguments, perhaps submitted as well
Options may be abbreviated as long as they are unambiguous so with my somewhat random sample above the program can be invoked for example as
prog.pl --conf filename -o original -o bare -g -25.0 data-file(s)
(Or whatever options for #AllConf are.) Providing explicitly g as another name for geom makes it a proper name, not an abbreviation (it doesn't "compete" with others).
Note that one can use -- or just -, and choose shorter or long(er) names for convenience or clarity etc. We get options, and there is a lot more than this little scoop, see docs.
Once the (named) options have been processed the rest on the command line is available in #ARGV, so one can mix and match arguments that way. Unnamed arguments are often used for file names. (The module offers a way to deal with those in some capacity as well.)
if ( #ARGV != 2 ) {
help();
}
my ( $path, $z ) = #ARGV;
perl gets input as list, so you can get its value using #ARGV or using $ARGV[0] and $ARGV[1]
die if ($#ARVG != 1);
#the first argument
print "$ARGV[0]\n";
#the second argument
print "$ARGV[1]\n"

perl parse command line arguments using shift command

I have a question regarding parsing command line arguments and the use of the shift command in Perl.
I wanted to use this line to launch my Perl script
/home/scripts/test.pl -a --test1 -b /path/to/file/file.txt
So I want to parse the command line arguments. This is part of my script where I do that
if ($arg eq "-a") {
$main::john = shift(#arguments);
} elsif ($arg eq "-b") {
$main::doe = shift(#arguments);
}
I want to use then these arguments in a $command variable that will be executed afterwards
my $var1=$john;
my $var2=$doe;
my $command = "/path/to/tool/tool --in $line --out $outputdir $var1 $var2";
&execute($command);
Now here are two problems that I encounter:
It should not be obligatory to specify -a & -b at the command line. But what happens now is that when I don't specify -a, I get the message that I'm using an uninitialized value at the line where the variable is defined
Second problem: $var2 will now equal $doe so it will be in this case /path/to/file/file.txt. However I want $var2 to be equal to --text /path/to/file/file.txt. Where should I specify this --text. It cannot be standardly in the $command, because then it will give a problem when I don't specify -b. Should I do it when I define $doe, but how then?
You should build your command string according to the contents of the variables
Like this
my $var1 = $john;
my $var2 = $doe;
my $command = "/path/to/tool/tool --in $line --out $outputdir";
$command .= " $var1" if defined $var1;
$command .= " --text $var2" if defined $var2;
execute($command);
Also
Don't use ampersands & when you are calling Perl subroutine. That hasn't been good practice for eighteen years now
Don't use package variables like $main:xxx. Lexical variables (declared with my) are almost all that is necessary
As Alnitak says in the comment you should really be using the Getopt::Long module to avoid introducing errors into your command-line parsing
GetOpt::Long might be an option: http://search.cpan.org/~jv/Getopt-Long-2.48/lib/Getopt/Long.pm
Regarding your sample:
You didn't say what should happen if -a or -b are missing, but defaults may solve your problem:
# Use 'join' as default if $var1 is not set
my $var1 = $john // 'john';
# Use an empty value as default if $var2 is not set
my $var2 = $doe // '';
Regarding the --text prefix:
Do you want to set it always?
my $command = "/path/to/tool/tool --in $line --out $outputdir $var1 --text $var2";
Or do you want to set it if -b = $var2 has been set?
# Prefix
my $var2 = "--text $john";
# Prefix with default
my $var2 = defined $john ? "--text $john" : '';
# Same, but long format
my $var2 = ''; # Set default
if ($john) {
$var2 = "--text $john";
}

How to go 1 level back for a directory path stored in a variable in perl

I am dynamically getting the path of a directory and it is getting stored in a variable. I need to go 1 level down and get the parent directory. Also, the path is a Windows path.
Basically , i am running perl on windows.
How to achieve this in perl ? Anything is fine - regular expression or any other way.
Please help!
For example :
Original directory path :
my $dir = "C:/mytest/mydata/mywork/mydir";
Output what is needed is :
my $dir = "C:/mytest/mydata/mywork";
I need till parent directory "mywork" , not "mydir".
Can anybody help in writing code for the same ?
Second query :
I am having space in the directory path:
example :
my $testdir = "C:/mytest own/mydata/mywork/mydir";
Notice, "mytest own" is one directory but with space.
Now, when i am trying to enter to use this variable to enter to this directory, i am unable to do so.
For query 1 , I tried the below piece of code :
#Ingo :
#!/usr/bin/perl
my $dir = "C:/mytest/mydata/mywork/mydir";
print "Input directory is : $dir\n";
my $outdir = $dir . "/..";
print "Output directory is : $outdir";
Note : I need to accomplish this, without using any perl module.
#TLP : I tried using your cut-short method, the code is again copied, but it doesn't give the desired output.
#!/usr/bin/perl
use strict;
use warnings;
use File::Spec;
my $str = "C:/mytest/mydata/mywork/mydir";
my #dirs = File::Spec->splitdir($str); # parse directories
my $newdir = File::Spec->catdir(#dirs, File::Spec->updir()); # create new path
print $newdir;
Output looks like : The output gets appended with /..
C:/mytest/mydata/mywork/mydir/..
Using a module for manipulating a path is perhaps a safer option. File::Spec has the convenient splitdir and catdir functions:
use strict;
use warnings;
use File::Spec;
my $str = "C:/mytest/mydata/mywork/mydir";
my #dirs = File::Spec->splitdir($str); # parse directories
pop #dirs; # remove top dir
my $newdir = File::Spec->catdir(#dirs); # create new path
print $newdir;
Note that the new path will by default use path delimiters that your current system uses.
You can also do
my $newdir = File::Spec->catdir(#dirs, File::Spec->updir());
(updir() returns ..) The output is the same on my system (Windows 7), which is to say
C:\mytest\mydata\mywork
I would have thought that it would be C:\mytest\mydata\mywork\mydir\.., but apparently the module abbreviated.
First, to get the parent directory, just append "/.."
$dir . "/.."
Second, are you using this in an external command or something? Then you must quote the path name, like in:
system "ls '$testdir'"
Otherwise, if you want to change directory in perl, the following should be fine:
chdir $testdir or die "Can't change to '$testdir' - $!"
Since you might want to know how to separate a directory path into a list of path elements, here is a subroutine which will separate the path. This is for linux, so you probably want to change the path separator regex,
#!/bin/perl
use strict;
use warnings;
#examine pathname, extract directory path
#examine pathname, extract directory path
sub dirname
{
my($pn) = #_;
my #p = split(/\//,$pn); ##windows would want split(/\\/,$pn)
if( $#p > 0 ) { pop(#p); push(#p,""); }
else { pop(#p); push(#p,"."); }
$pn = join('/',#p); ##you would want join('\\',#p)
return($pn);
};
my $dir=$ARGV[0] || "/a/b/c/d/e/f/g/h/i/j";
while ( $dir && length($dir)>1) {
print "$dir\n";
$dir = dirname($dir);
}
use this code
my $dir = "C:/mytest/mydata/mywork/mydir";
my $char = '/';
my $result = rindex($dir, $char);
$path = substr("$dir", 0,$result);

How to properly escape characters for backquotes for ssh in Perl?

I have this code:
$script = "console.log(\"It works!\");";
$output = qx/ssh user#123.123.123.123 $script | interpreter/;
It's supposed to run $script through interpreter and write it into $output. The problem is that it doesn't work. How do I escape the characters correctly?
Think about what you're trying to do just with ssh. Both of these produce the same output, but work differently:
ssh user#host 'echo "puts 2+2" | ruby'
echo "puts 2+2" | ssh user#host ruby
In the first, the remote shell is executing the pipeline. (If you don't have those single quotes, what happens?) In the second, it's piped through your local shell to the ssh command and the interpreter launched.
Rather than perform convoluted escapes of code to come out correctly when crammed through sh, I prefer to pipe the text in through stdin. It's just simpler.
Using IPC::Run to do all the heavy lifting:
#!/usr/bin/env perl
use strict;
use warnings;
use IPC::Run qw(run);
my $in = <<FFFF;
2 + 2
8 * (2 + 3)
FFFF
my $cmd = [qw(ssh user#host perl calc.pl)];
run $cmd, \$in, \my $out, \my $err or die "ssh: $?";
print "out: $out";
print "err: $err";
(calc.pl is a simple infix calculator program that I had lying around)
Here's the output I get, running that:
out: 4
40
err: (SSH banners and pointless error messages)
Seeing system or qx// calls in a perl script is a sign of trouble. In general, I don't like having to think about shell syntax or shell quoting when I'm not writing shell; it has nothing to do with the problem I'm trying to solve, nor the tool I'm solving it with. (And that's ignoring any security implications.)
Oh, and if you don't have to muck with standard input but still want to execute and capture output from other programs, there's always IPC::System::Simple.
Since you're using Perl, you should do it in Perl, and not a call out to an external command.
Have you tried the Perl module Net::SSH::Perl?
I would also use qq instead of quotation marks when setting the value of $script. Using qq removes the whole how do I quote quotes mess. What ever character comes after qq is your string delimiter. All of these are valid:
my $string = qq/This is a "string" with a quote/;
my $string = qq|This is a "string" with a quote|;
my $string = qq*This is a "string" with a quote*;
Special matching quote operators are ( and ), [ and ], and { and }:
my $string = qq(This (is (a "string" with) a) quote);
Note that I can use parentheses as my string delimiters even though my string has parentheses in it. This is okay as long as those parentheses are balanced. This one wouldn't work:
my $string qq(This is an "unbalanced" parentheses ) that breaks this statement);
But, then I can switch to square brackets or curly braces:
my $string qq[This is an "unbalanced" parentheses ) but still works.];
Here's a Perl version of your program:
use strict; #Always use!
use warnings; #Always use!
use Net::SSH::Perl;
#
# Use Constants to set things that are ...well... constant
#
use constant {
HOST => "123.123.123.123",
USER => "user",
};
my $script = qq[console.log("It works!");];
my $connection = Net::SSH::Perl->new(HOST);
$connection->login(USER);
my ($output, $stderr, $exit_code) = $connection->cmd($script);
use Net::OpenSSH:
my $ssh = Net::OpenSSH->new('user#123.123.123.123');
my $output = $ssh->capture({stdin_data => 'console.log("It works!");'},
'interpreter');

Perl: how can i pass list to script?

i need to pass list to my script
how can i do it?
for example i have script: flow.pl
i need to pass to it list of fubs :
fub1, fub2, fub3
and path to database1 =
path1
and path to database2 =
path2
and how can i get this parameters?
(through $ARGV[i]?)
if i do:
flow.pl fub1,fub2,fub3 path1 path2
and in the code:
$listoffubs = $ARGV[0]
$path1 = $ARGV[1]
$path2 = $ARGV[2]
list of fubs get name of fubs like one word.
Having lists of positional arguments is ok as long as the lists are short and simple. Once you get into a larger number of arguments or you have arguments with an internal structure, then you probably want to look at named arguments.
In this case, I think I'd be looking at using GetOpt::Long to implement named arguments.
Simply split the list:
my #ListOfFubs = split /,/ , $listoffubs;
Your arguments will be space separated, so yes, fub1,fub2,fub3 will be one argument. Just use space instead, and you'll be fine. E.g.:
flow.pl fub1 fub2 fub3 path1 path2
my $fub1 = shift; # first argument is removed from ARGV and assigned to $fub1
Or
my $fub1 = $ARGV[0]; # simple assignment
All at once
my ($fub1,$fub2,$fub3,$path1,$path2) = #ARGV; # all are assigned at once
Note that using shift is removing arguments from #ARGV.
If you have a list of arguments that may vary, it is a simple fix to put them last, and then do:
flow.pl path1 path2 fub1 fub2 fub3 ... fubn
my $path1 = shift;
my $path2 = shift; # remove first two arguments, then
my #fubs = #ARGV; # assign the rest of the args to an array
For more complicated argument handling, use a module, such as Getopt::Long.