I'm setting up some automation using shell and applescript scripting. Several of my modules require passing several params into the AppleScript. Although there are a few ways of doing so, a bit of research lead me to the "Mac::AppleScript Perl module" and it looks really promising. However it requires a short .pl script in /usr/local/bin. The only example of it I found has two syntax errors and I have absolutely no experience at all in Perl.
I've tried guessing at what might be throwing the errors based on what I would use in the languages I've worked with but even though I can recognize the variables not expanding in a few spots, my attempts at correcting it results in further errors and I've been hacking at it for hours. Can someone please take a look and tell me what might be the syntax issues I can fix?
#!/usr/bin/perl
use strict;
use Mac::AppleScript qw/ RunAppleScript /;
if( $#ARGV = 0) ? '"'.join('","',#ARGV).'"' : "";
my $rtn = RunAppleScript( "return run script alias ((POSIX file ""
.$script.'") as text) with parameters {'.$args.'}' ),"n"
or die "AppleScript Error: $!";
$rtn =~ s/(^"|"$)//g;
print $rtn,"n";
Problems:
You have an unescaped " in your double-quoted string literal.
You use variables you never declared or initialized.
You have a stray ,"n" in your code.
Your code suffers from code injection bugs.
The error message is found in $#, not $!.
Fixed:
sub text_to_as_lit { '"'.( $_[0] =~ s/([\\"])/\\$1/rg ).'"' } #'
my ($script, #args) = #ARGV;
my $rtn = RunAppleScript(sprintf(
"return run script alias ((POSIX file %s) as text) with parameters {%s}",
text_to_as_lit($script),
( join ',', map text_to_as_lit($_), #args ),
))
or die("AppleScript Error: $#");
Untested.
Related
I'm trying to run the fping script described in its man page.
#!/usr/local/bin/perl
require 'open2.pl';
$MAILTO = "root";
$pid = &open2("OUTPUT","INPUT","/usr/local/bin/fping -u");
#check=("slapshot","foo","foobar");
foreach(#check) { print INPUT "$_\n"; }
close(INPUT);
#output=;
if ($#output != -1) {
chop($date=`date`);
open(MAIL,"|mail -s 'unreachable systems' $MAILTO");
print MAIL "\nThe following systems are unreachable as of: $date\n\n";
print MAIL #output;
close MAIL;
}
However, I get the following error from anywhere I run it:
syntax error at /path/to/pingtest.pl line 13, near "=;"
Execution of /path/to/pingtest.pl aborted due to compilation errors.
Can someone help me what's wrong with line 13? I have open2.pl and fping path is correct.
If you have found the script on the online man-page, then <OUTPUT> has been interpreted as HTML markup and deleted. It should read
#output = <OUTPUT>;
But this Perl script looks like it was written decades ago
The use of require open2.pl was long-ago replaced by use IPC::Open2
It does not use use strict or use warnings, and avoids lexical variables
Function calls use the arcane &open2 syntax, which is useful only in very special cases
The calls to open use the old-fashioned and ambiguous two-argument-version
While digging in some old source code I saw the following:
my $module = $some{module};
eval "require $module";
die "Bad module\n$#" if $#;
while I understand what the code does, it tries "require" a module and die when it is unsuccessful - the perlcritic complains about it
Expression form of "eval" at line 331, column 13. See page 161 of
PBP. (Severity: 5)
Unfortunately I havent the PBP book, so wondering what is the correct method of the above...
Also, in the same source found:
sub test_repo_file {
my($self, $repo, $test) = #_;
my $abspath = repo_abs_path($repo);
return "eval -$test $abspath";
}
Here doesn't understand what solves the "eval", and the perlcritic complains again for the "string eval"...
Can somebody please explain the basic points about the "string eval" and how to write the above correctly?
Running perlcritic --verbose '%d\n' will give you the explanations, too:
The string form of `eval' is recompiled every time it is executed,
whereas the block form is only compiled once. Also, the string form
doesn't give compile-time warnings.
eval "print $foo"; # not ok
eval {print $foo}; # ok
It's applicable to the first case.
The second case doesn't generate any messages for me. Isn't it rather
return eval "-$test $abspath"
You can't use block eval here. You should verify that $test really contains what it should
$test =~ /^[a-z]$/i
and avoid the evaluation of $abspath:
eval "-$test \$abspath"
If you're OK with that, you can than add
## no critic
to the end of the line.
Few users should ever have to use eval EXPR. Most of the time, it's being used as a template system when it shouldn't (e.g. s/.../eval($repl)/e), or it's used to catch exceptions when eval BLOCK should have been used.
If there is reason to use it, it's to execute generated code or user-submitted code. Generating code is tricky and error-prone, and errors have security implications. Executing user-submitted code is a major security concern. As such, every use of eval EXPR should be carefully reviewed.
It's quite appropriate for perlcritic to flag its usage given that virtually every use is an error with major security implications.
In your case, the use of eval EXPR is suboptimal. I'd use
my $path = $module . ".pm";
$path =~ s{::}{/}g;
eval { require $path }
Yes, this is portable.
I'm facing an issue using eval function.
Indeed I have some function name inside a SQL database, my goal is to execute those functions within perl (after retrieve in SQL).
Here is what I'm doing, considering that $RssSource->{$k}{Proceed} contains "&test" as a string retrieved from SQL:
my $str2 = "ABCD";
eval "$RssSource->{$k}{Proceed}";warn if $#;
sub test
{
my $arg = shift;
print "fct TEST -> ", $row, "\n";
}
This is working correctly and display:
fct TEST ->
However I would like to be able to pass $str2 as an argument to $RssSource->{$k}{Proceed} but I don't know how, every syntax I tried return an error:
eval "$RssSource->{$k}{Proceed}$str2"
eval "$RssSource->{$k}{Proceed}($str2)"
eval "$RssSource->{$k}{Proceed}"$str2
eval "$RssSource->{$k}{Proceed}"($str2)
May someone tell me how to properly pass an argument to the evaluated function?
Thanks a lot for your help
Regards.
Florent
I'm going to assume that $RssSource->{$k}{Proceed} always contain name or &name, otherwise what you are asking doesn't make much sense.
my $func_name = $RssSource->{$k}{Proceed};
$func_name =~ s/&//;
my $func_ref = \&$func_name; # Works with strict on!
$func_ref->(#args);
If you want to add some error checking, the following will check if the sub can be called:
defined(&$func_ref)
If the string you are evaling always is a sub invocation, you can construct the eval string in one of these ways:
$RssSource->{$k}{Proceed} . '($str2)'
(most general), or
$RssSource->{$k}{Proceed} . "(\"$str2\")"
(inelegant)
Here are the problems your solutions ran into:
eval "$RssSource->{$k}{Proceed}$str2" evaluates to eval "&testABCD". This sub doesn't exist.
eval "$RssSource->{$k}{Proceed}($str2)" evaluates to "&test(ABCD)". Bareword not allowed.
eval "$RssSource->{$k}{Proceed}"$str2 A string has to be followed by some sort of operator, not another variable.
eval "$RssSource->{$k}{Proceed}"($str2) You are trying to call a string as a function. This is not supported in Perl.
If you can change the data in your database to contain just a function name, that is, test rather than &test, you can call a function by a symbolic reference, rather than using eval:
$fn="test";
&{$fn}("argument")
You do not need eval if, as you say, your database just contains function names. You can use them as symbolic references (but please remove the &). The modern way to do it would not be using the & to dereference it but to use the arrow operator:
{
no strict 'refs'; # hopefully you have strict on already...
$RssSource->{$k}{Proceed}->($str2);
}
Very similar to ikegami's answer, using the can method, which is more my taste. TIMTOWTDI.
my $func_name = $RssSource->{$k}{Proceed};
$func_name =~ s/&//;
my $func_ref = __PACKAGE__->can($func_name)
or die "No function named $func_name";
$func_ref->(#args);
Perl docs recommend this:
$foo = $bar =~ s/this/that/r;
However, I get this error:
Bareword found where operator expected near
"s/this/that/r" (#1)
This is specific to the r modifier, without it the code works.
However, I do not want to modify $bar.
I can, of course, replace
my $foo = $bar =~ s/this/that/r;
with
my $foo = $bar;
$foo =~ s/this/that/;
Is there a better solution?
As ruakh wrote, /r is new in perl 5.14. However you can do this in previous versions of perl:
(my $foo = $bar) =~ s/this/that/;
There's no better solution, no (though I usually write it on one line, since the s/// is essentially serving as part of the initialization process:
my $foo = $bar; $foo =~ s/this/that/;
By the way, the reason for your error-message is almost certainly that you're running a version of Perl that doesn't support the /r flag. That flag was added quite recently, in Perl 5.14. You might find it easier to develop using the documentation for your own version; for example, http://perldoc.perl.org/5.12.4/perlop.html if you're on Perl 5.12.4.
For completeness.
If you are stuck with an older version of perl.
And really want to use the s/// command without resorting to using a temporary variable.
Here is one way:
perl -E 'say map { s/_iter\d+\s*$//; $_ } $ENV{PWD}'
Basically use map to transform a copy of the string and return the final output.
Instead of what s/// does - of returning the count of substitutions.
I'm not necessarily looking for a better way to do this, rather an explanations of the output would greatly be appreciated. Recently, a senior programmer asked me why his code worked but only for one instance. What I came to find out was that it worked every other occurrence. Here is my example:
#!/usr/bin/perl -w
use strict;
my #list_env_vars = (
'$SERVER',
'$SERVER',
'$SERVER',
'$SERVER',
'$SERVER',
'$SERVER',
);
foreach (#list_env_vars){
print "$_ = ".glob()."\n";
}
which output for perl 5.004:
$SERVER = UNIX_SERVER
$SERVER =
$SERVER = UNIX_SERVER
$SERVER =
$SERVER = UNIX_SERVER
$SERVER =
or output for perl 5.10:
$SITE = $SITE
Use of uninitialized value in concatenation (.) or string at glob_test.pl line 14.
$SITE =
$SITE = $SITE
Use of uninitialized value in concatenation (.) or string at glob_test.pl line 14.
$SITE =
$SITE = $SITE
Use of uninitialized value in concatenation (.) or string at glob_test.pl line 14.
$SITE =
I personally have never used glob() in this fashion so I was ill equipped to answer him. I read through perldoc glob documentation and followed the File::Glob link on that page and still couldn’t find anything that would explain the output. Any help would be much appreciated.
glob in scalar context:
In scalar context, glob iterates through such filename expansions, returning undef when the list is exhausted.
In
foreach (#list_env_vars){
print "$_ = ".glob()."\n";
}
The glob() there really is glob($_). Every iteration, $_ contains the string $SERVER. Given that the environment variable does not change, $SERVER is expanded to the same string. First time, this string is returned. Next, the list is exhausted, so undef is returned. Third time, we start over. ...
Clarification: It does not matter that the argument to the second call is the same as the one for the first call since there is no way to reset glob's iterator.
You can see this more clearly using the following example (current directory contains files '1.a', 1.b', '2.a' and '2.b'):
#!/usr/bin/perl -w
use strict;
my #patterns = (
'*.a',
'*.b',
);
for my $v ( #patterns ) {
print "$v = ", scalar glob($v), "\n";
}
Output:
C:\Temp> d
*.a = 1.a
*.b = 2.a
I would recommend accessing environment variables via the %ENV hash:
my #list_env_vars = ($ENV{SERVER}) x 6;
or
my #list_env_vars = #ENV{qw(HOME TEMP SERVER)};
Incidentally, the reason why in 5.004 you get a variable expansion, while on 5.10 you just get your literal string back, is because on old perl, glob() was carried out by the system shell, which just as a side-effect performs variable expansion. Since perl 5.6, glob() uses the File::Glob module which does the work itself, without the shell, and doesn't expand environment variables (which glob was never intended to do). %ENV is the proper way to get at the environment.
Notes on the old behavior, wiki'd for your convenience (and so that I have the full range of markup and no 500-char limit):
The fact that glob and <*globbything*> changed in 5.6 is mentioned in passing in the docs (perl56delta, perlop, -f glob) but the only real source on exactly how it used to work is a pre-5.6 version of perlop. Here's the relevant bit from 5.005:
Example:
while (<*.c>) {
chmod 0644, $_;
}
is equivalent to
open(FOO, "echo *.c | tr -s ' \t\r\f' '\\012\\012\\012\\012'|");
while (<FOO>) {
chop;
chmod 0644, $_;
}
In fact, it's currently implemented that way. (Which means it will not work on filenames with spaces in them unless you have csh(1) on your machine.)
Heh, that's pretty evil stuff. Anyway, if you ever find yourself wanting to consult old perldocs like that, just go to search.cpan.org, pull up the perl distribution, use the pulldown list to select an old version, then click through to the doc that you need. perl itself isn't really subject to getting "tidied" off of CPAN; currently everything from 5.004 on up is available without hitting BackPan.