Is there a way to check, if an argument is passed in single quotes? - perl

Is there a (best) way to check, if $uri was passed in single quotes?
#!/usr/local/bin/perl
use warnings;
use 5.012;
my $uri = shift;
# uri_check
# ...
Added this example, to make my problem more clear.
#!/usr/local/bin/perl
use warnings;
use 5.012;
use URI;
use URI::Escape;
use WWW::YouTube::Info::Simple;
use Term::Clui;
my $uri = shift;
# uri check here
$uri = URI->new( $uri );
my %params = $uri->query_form;
die "Malformed URL or missing parameter" if $params{v} eq '';
my $video_id = uri_escape( $params{v} );
my $yt = WWW::YouTube::Info::Simple->new( $video_id );
my $info = $yt->get_info();
my $res = $yt->get_resolution();
my #resolution;
for my $fmt ( sort { $a <=> $b } keys %$res ) {
push #resolution, sprintf "%d : %s", $fmt, $res->{$fmt};
}
# with an uri-argument which is not passed in single quotes
# the script doesn't get this far
my $fmt = choose( 'Resolution', #resolution );
$fmt = ( split /\s:\s/, $fmt )[0];
say $fmt;

You can't; bash parses the quotes before the string is passed to the Perl interpreter.

To expand on Blagovest's answer...
perl program http://example.com/foo?bar=23&thing=42 is interpreted by the shell as:
Execute perl and pass it the arguments program and http://example.com/foo?bar=23
Make it run in the background (that's what & means)
Interpret thing=42 as setting the environment variable thing to be 42
You should have seen an error like -bash: thing: command not found but in this case bash interpreted thing=42 as a valid instruction.
The shell handles the quoting and Perl has no knowledge of that. Perl can't issue an error message, it just sees arguments after shell processing. It never even sees the &. This is just one of those Unix things you'll have to learn to live with. The shell is a complete programming environment, for better or worse.
There are other shells which dumb things down quite a bit so you can avoid this issue, but really you're better off learning the quirks and powers of a real shell.

Related

How to get array of hash arguments using Getopt::Long lib in perl?

I want to take arguments as an array of hashes by using Getopt::Long in my script.
Consider the following command line example:
perl testing.pl --systems id=sys_1 ip_address=127.0.0.1 id=sys_2 ip_address=127.0.0.2
For the sake of simplicity, I'm using two systems and only two sub arguments of each system, i.e., id and ip_address. Ideally, the number of systems is dynamic; it may contain 1, 2 or more and so with the number of arguments of each system.
My script should handle these arguments in such a way that it will store in #systems array and each element will be a hash containing id and ip_address.
Is there any way in Getopt::Long to achieve this without parsing it myself?
Following is pseudocode for what I'm trying to achieve:
testing.pl
use Getopt::Long;
my #systems;
GetOptions('systems=s' => \#systems);
foreach (#systems) {
print $_->{id},' ', $_->{ip_address};
}
Here is an attempt, there might be more elegant solutions:
GetOptions('systems=s{1,}' => \my #temp );
my #systems;
while (#temp) {
my $value1 = shift #temp;
$value1 =~ s/^(\w+)=//; my $key1 = $1;
my $value2 = shift #temp;
$value2 =~ s/^(\w+)=//; my $key2 = $1;
push #systems, { $key1 => $value1, $key2 => $value2 };
}
for (#systems) {
print $_->{id},' ', $_->{ip_address}, "\n";
}
Output:
sys_1 127.0.0.1
sys_2 127.0.0.2
I actually think this is a design problem, more than a problem with GetOpt - the notion of supporting multiple, paired arguments passed as command line arguments I think is something that you'd be far better off avoiding.
There's a reason that GetOpt doesn't really support it - it's not a scalable solution really.
How about instead just reading the values from STDIN?:
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
my %systems = do { local $/; <DATA> } =~ m/id=(\w+) ip_address=([\d\.]+)/mg;
print Dumper \%systems;
And then you'd be able to invoke your script as:
perl testing.pl <filename_with_args>
Or similar.
And if you really must:
my %systems = "#ARGV" =~ m/id=(\w+) ip_address=([\d\.]+)/g;
Both of the above work for multiple parameters.
However, your comment on another post:
I can't because I'm fetching parameters from database and converting them into command line and then passing it to the script using system command $cmd_lines_args = '--system --id sys_1 --ip_address 127.0.0.0.1'; system("perl testing.pl $cmd_lines_args"); $cmd_lines_args I'll generate dynamically using for loop by reading from database
.. that makes this an XY Problem.
Don't do it like that:
open ( my $script, '|-', "perl testing.pl" );
print {$script} "id=some_id ip_address=10.9.8.7\n";
print {$script} "id=sys2 ip_address=10.9.8.7\n";
etc.
What you are describing,
--systems id=sys_1 ip_address=127.0.0.1 id=sys_2 ip_address=127.0.0.2
appears to be one option that takes a variable number of arguments that are pairs, and come in multiples of two. Getopt::Long's "Options with multiple values" lets you do the following:
GetOptions('systems=s{2,4}' => \#systems);
This lets you specify 2, 3 or 4 arguments, but it does not have syntax for "any even number of arguments" (to cover an arbitrary number of pairs beyond two), and you still have to unpack the id=sys_1 manually then. You can write a user-defined subroutine that handles the processing of --systems' arguments (but does not take into account missing id=...s):
my $system;
my %systems;
GetOptions('systems=s{,}' => sub {
my $option = shift;
my $pair = shift;
my ($key, $value) = split /=/, $pair;
$system = $value if $key eq 'id';
$systems{$system} = $value if $key eq 'ip_address';
});
I would however prefer one of the following schemes:
--system sys_1 127.0.0.1 --system sys_2 127.0.0.1
--system sys_1=127.0.0.1 --system sys_2=127.0.0.1
They're achieved with the following:
GetOptions('system=s{2}', \#systems);
GetOptions('system=s%', \#systems);
I would just parse the --systems arg and quote the "hashes" like this:
perl testing.pl --systems "id=s1 ip_address=127.0.0.1 id=s2 ip_address=127.0.0.2"
Parse like perhaps so:
my($systems,#systems);
GetOptions('systems=s' => \$systems);
for(split/\s+/,$systems){
my($key,$val)=split/=/,$_,2;
push #systems, {} if !#systems or exists$systems[-1]{$key};
$systems[-1]{$key}=$val;
}
print "$_->{id} $_->{ip_address}\n" for #systems;
Prints:
sys_1 127.0.0.1
sys_2 127.0.0.2

Perl parameters passing with special characters

This is a pure Perl parameters passing issue. I cannot use Get::Opt as it is not installed on every machine.
I need to pass parameters with spaces and other special chars sometimes. Three scripts to demo the process. Is there a better way to do this?
[gliang#www stackoverflow]$ perl parameter_wrapper.pl
prep.pl #<5> parameters
prep_v2.pl #<5> parameters
<aaa_777-1>
<bbb-6666-2>
<Incomplete QA>
<-reason>
<too long, mail me at ben#example.com :)>
cat parameter_wrapper.pl
#!/usr/bin/perl -w
use strict;
# call prep.pl with 5 parameters
my $cmd = "./prep.pl aaa_777-1 bbb-6666-2 'Incomplete QA' -reason 'too long, mail me at ben\#example.com :)\n'";
system($cmd);
cat prep.pl
#!/usr/bin/perl -w
use strict;
my #parameters = #ARGV;
my $count = scalar(#parameters);
my #parameters_new = wrap_parameters(#parameters);
my $cmd = "./prep_v2.pl #parameters_new";
print "prep.pl #<$count> parameters\n";
system($cmd);
sub wrap_parameters {
my #parameters = #_;
my #parameters_new;
foreach my $var(#parameters) {
$var = quotemeta($var);
push(#parameters_new, $var);
}
return #parameters_new;
}
cat prep_v2.pl
#!/usr/bin/perl -w
use strict;
my #parameters = #ARGV;
my $count = scalar(#parameters);
print "prep_v2.pl #<$count> parameters\n";
foreach my $var (#parameters) {
#print "<$var>\n";
}
Getopt::Long has been part of the Perl core since Perl 5 was first released in 1994. Are you sure it's not available on the machines you're looking to deploy on? In your comment you refer to it as "Get::Opt", so could you have made a mistake while checking the machines?

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-SQLite3: Basic Question

I admit it's been a while since I've used Perl, but this has me stumped.
Here's the issue:
#!/usr/bin/perl
use CGI::Carp qw(fatalsToBrowser);
use DBI;
print "Content-type: text/html\n\n";
print "<html><head><title></title></head></body>";
my $login = "admin#xxxxx.com";
my $dbfile = "/var/www/shopsite-data/shopsite_db";
my $sql = qq`SELECT ss_OrderID FROM ss_ORDER WHERE ss_Email=?`;
my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", "", "") || die "Cannot connect: $DBI::errstr";
my $sth = $dbh->prepare($sql);
$sth->execute($login) or die $sth->errstr();
while (my #result = $sth->fetchrow_array()) {
print "OrderID: $result[0]<br />";
}
$sth->finish;
print "</body>";
print "</html>";
$dbh->disconnect;
returns nothing, but I get a resultset when logged in with sqlite3 using the same query. I also get a resultset when I change the query from
my $sql = qq`SELECT ss_OrderID FROM ss_ORDER WHERE ss_Email=?`;
to
my $sql = qq`SELECT ss_OrderID FROM ss_ORDER`;
The obvious problem is the # inside the double quotes:
my $login = "admin#xxxxx.com";
is probably coming out as
$login = "admin.com"
and, if you had warnings switched on, a warning would be printed to the log file, because Perl sees #xxxx as an array and tries to interpolate it, then warns because it is empty. That is, assuming you don't have an array called #xxxx. If you do, then you would get all the values of it in the string.
Where you have the email address, use single quotes around it to prevent #xxxx being interpolated as an array:
my $login = 'admin#xxxxx.com';
Or you could use
my $login = "admin\#xxxxx.com";
to prevent the # starting an interpolation.
There may be other problems with your script but this is the most obvious one.
Strangely enough I was just reading about drawbacks of interpolation in Perl.
One more thing ...
One more thing: you already have fatalsToBrowser, but
use warnings;
use strict;
use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
would probably have given you a warning on your browser about uninitialized values, so it might be worth turning warningsToBrowser on until your script seems to be working (or if it stops working again) (documentation here), and the other two on always.
I believe Kinopiko already pinpointed the problem.
I will add that, if you are going to use CGI.pm, you should not generate headers by hand. Instead, use CGI::header.
Also:
print "<html><head><title></title></head></body>";
Note the closing tag for body when you meant to use an opening tag.
Last, but definitely not least, you should
use strict;
use warnings;
in your scripts.

What's an easy way to print a multi-line string without variable substitution in Perl?

I have a Perl program that reads in a bunch of data, munges it, and then outputs several different file formats. I'd like to make Perl be one of those formats (in the form of a .pm package) and allow people to use the munged data within their own Perl scripts.
Printing out the data is easy using Data::Dump::pp.
I'd also like to print some helper functions to the resulting package.
What's an easy way to print a multi-line string without variable substitution?
I'd like to be able to do:
print <<EOL;
sub xyz {
my $var = shift;
}
EOL
But then I'd have to escape all of the $'s.
Is there a simple way to do this? Perhaps I can create an actual sub and have some magic pretty-printer print the contents? The printed code doesn't have to match the input or even be legible.
Enclose the name of the delimiter in single quotes and interpolation will not occur.
print <<'EOL';
sub xyz {
my $var = shift;
}
EOL
You could use a templating package like Template::Toolkit or Text::Template.
Or, you could roll your own primitive templating system that looks something like this:
my %vars = qw( foo 1 bar 2 );
Write_Code(\$vars);
sub Write_Code {
my $vars = shift;
my $code = <<'END';
sub baz {
my $foo = <%foo%>;
my $bar = <%bar%>;
return $foo + $bar;
}
END
while ( my ($key, $value) = each %$vars ) {
$code =~ s/<%$key%>/$value/g;
}
return $code;
}
This looks nice and simple, but there are various traps and tricks waiting for you if you DIY. Did you notice that I failed to use quotemeta on my key names in the substituion?
I recommend that you use a time-tested templating library, like the ones I mentioned above.
You can actually continue a string literal on the next line, like this:
my $mail = "Hello!
Blah blah.";
Personally, I find that more readable than heredocs (the <<<EOL thing mentioned elsewhere).
Double quote " interpolates variables, but you can use '. Note you'll need to escape any ' in your string for this to work.
Perl is actually quite rich in convenient things to make things more readable, e.g. other quote-operations. qq and q correspond to " and ' and you can use whatever delimiter makes sense:
my $greeting = qq/Hello there $name!
Nice to meet you/; # Interpolation
my $url = q|http://perlmonks.org/|; # No need to escape /
(note how the syntax coloring here didn't quite keep up)
Read perldoc perlop (find in page: "Quote and Quote-like Operators") for more information.
Use a data section to store the Perl code:
#!/usr/bin/perl
use strict;
use warnings;
print <DATA>;
#print munged data
__DATA__
package MungedData;
use strict;
use warnings;
sub foo {
print "foo\n";
}
Try writing your code as an actual perl subroutine, then using B::Deparse to get the source code at runtime.