Perl Syntax Error: bareword found where operator expected - perl

This is my perl script code. in this i'm getting error like "bareword found where operator expected at $cmd"
my $path = $folder."/".$host."_".$database_name.".bak";
$cmd .= "-U $user_name -P $password -S $host -d master -Q "BACKUP DATABASE [$database_name] TO DISK = N'$path'" ";
any one help me?

When a string has double quotes within it, you need to escape them with \.
$cmd .= "-U $user_name -P $password -S $host -d master -Q \"BACKUP DATABASE [$database_name] TO DISK = N'$path'\" ";
Also, Perl lets you use other characters for quote delimiters. qq followed by almost any character is the same as double quotes. So you could do things like this to avoid the need of backslashes:
$cmd .= qq(-U $user_name -P $password -S $host -d master -Q "BACKUP DATABASE [$database_name] TO DISK = N'$path'" );
$cmd .= qq|-U $user_name -P $password -S $host -d master -Q "BACKUP DATABASE [$database_name] TO DISK = N'$path'" |;
And so on...
Update: How to execute a system command in Perl. There are three basic ways:
system($cmd); #Goes through the shell if shell metacharacters are detected.
system(#command_and_args); #first element is the command, the rest are arguments
system executes a command and waits for it to return. The return value is the exit status of the program.
my #results = `$cmd`; #Always goes through shell.
Backticks execute a command and return its output. You should only use this if you actually need the output; otherwise, it is better to go with system.
exec $cmd;
exec #command_and_args;
exec is exactly like system, except that it never returns. It effectively ends your program by calling another program.
Use the one that is most appropriate to your situation. Or in this case, since you are executing SQL, consider using the DBI module. It's definitely a better approach for anything more than a couple of simple commands.

Looks like you have your " characters in the wrong place. I'm not sure where they should be.
In your second line, the string literal:
"-U $user_name -P $password -S $host -d master -Q "
is immediately followed by the bareword
BACKUP

Related

awk command in Perl's system does not work

I am writing a small Perl script that executes an Awk command :
I try to swap two columns in a file, the file is like this :
domain1,ip1
domain2,ip2
domain3,ip3
the result should be
ip1,domain1
ip2,domain2
ip3,domain3
The Perl command invoking awk is like this:
system("ssh -p 22 root\#$mainip 'awk -F, '{print $2,$1}' OFS=, /root/archive/ipdomain.txt > /root/ipdom.txt'");
This is the error I get :
awk: cmd. line:1: {print
awk: cmd. line:1: ^ unexpected newline or end of string
any suggestions, please?
With the layered commands and all that multi-level quoting and escaping that need be done right,† no wonder it fails. A complex command like that will always be tricky, but libraries help a lot.
A properly quoted string to run through a shell can be formed with String::ShellQuote ‡
use warnings;
use strict;
use feature 'say';
use String::ShellQuote qw(shell_quote);
die "Usage: $0 file outfile\n" if #ARGV != 2;
my ($file, $out) = #ARGV;
my #cmd_words =
( 'ssh', 'hostname', 'awk', q('{print $2 $1}'), $file, '>', $out );
my $cmd = shell_quote #cmd_words;
system($cmd);
Note how the q() operator from of single quotes enables us to pass single quotes nicely.
This swaps the first two words on each line of a file and prints them, using awk, and redirects the output to a file, on a remote host. It works as expected in my tests (with a real hostname). Please adjust as needed.
Another possible improvement would be to use a library for ssh, like Net::OpenSSH.
A complete command, as the one in the question, to use in the above program
my #cmd_words = (
'ssh', '-p', '22', "root\#$mainip",
'awk', '-F,', q('{print $2,$1}'), 'OFS=,', $file, '>', $out );
Tested with a file from the question.
The makeVoiceBot answer is informative and it got half way there but I find the need for
system("ssh hostname \"awk '{print \\\$2 \\\$1}' $path\"");
This works in my tests (on systems I ssh to). I try to avoid needing to deal with such quoting and escaping.
† This is a shell command which runs ssh, and then executes a command on the remote system which runs a shell (there) as well, in order to run awk and redirect its output to a file.
A bit more than an "awk command" as the title says.
‡ The library can prepare a command for bash (as of this writing), but one can look at the source for it and adjust it for their own shell, at least. There is also Win32::ShellQuote
I am using a shortened example here
system("ssh localhost 'awk '{print $2,$1}' file.txt'")
system() sees:
ssh localhost 'awk '{print $2,$1}' file.txt'
local shell expands:
ssh
localhost
awk
{print
$2,$1}
file.txt
local shell replaces $1 and $2 (positional args) with empty strings:
ssh
localhost
awk
{print
,}
file.txt
ssh executes:
ssh localhost awk {print ,} file.txt
remote shell gets:
awk
{print
,}
file.txt
So the remote shell runs awk with {print as its program argument, resulting in the described error. To prevent this, the invocation of system() can be changed to;
system("ssh localhost \"awk '{print \$2,\$1}' file.txt\"")
system() sees:
ssh localhost "awk '{print \$2,\$1}' file.txt"
local shell expands:
ssh
localhost
awk '{print \$2,\$1}' file.txt
ssh executes
ssh localhost awk '{print \$2,\$1}' file.txt
remote shell gets
awk
{print \$2,\$1}
file.txt
remote shell expands \ escapes
awk
{print $2,$1}
file.txt
Remote awk now gets {print $2,$1} as its program argument, and executes successfully.

Can't figure out how to properly execute exe with arguments

So I have been trying to write a script where I run an executable with arguments to return some information I need for our monitoring. Not long ago I have written a script that successfully managed to do that, using the following command:
$command = "-t vtl -p {1} -m {0} -s"
$check = Invoke-Expression -command ("$exemap\$exe $command" -f $server,$poort)
This worked perfectly. However I am in the process of writing a new script and the exact same thing does not seem to be working anymore. Here is a script I have to reproduce the problem:
$omgeving = "A"
$exemap = "\\cfc01.corp\rpa-{0}\Scripts\Check_actualiteit_RPA" -f $omgeving
#$exe = "echoargs.exe"
$exe = "stmmon.exe"
$exepath = "$exemap\"+"$exe"
$commando = "-t {0} -p {1} -m {2} -s"
$poort = "5062"
$berichttype = "VTL"
$server = "rpa-a-ap001"
$command = ($commando -f $berichttype,$poort,$server) -split " "
#$command = "-t VTL -p 5062 -m rpa-a-ap001 -s"
& $exepath $command
So I have negated some lines that are there for troubleshooting. I have tried using the Invoke-Expression command above and using the & operator. I have googled and tried a lot. I have tried using jobs and Invoke-Command. But all of them have the same result, an explanation from the exe telling me how to use it:
Usage: \\cfc01.corp\rpa-A\Scripts\Check_actualiteit_RPA\stmmon.exe [-v] [-h] -t <type> -p <port> -m <machine> -k <key> [-s]
This makes me believe that the executable is not really acknowleding any arguments. I tried using echoargs and the output from the script above is:
Arg 0 is <-t>
Arg 1 is <VTL>
Arg 2 is <-p>
Arg 3 is <5062>
Arg 4 is <-m>
Arg 5 is <rpa-a-ap001>
Arg 6 is <-s>
Or, if I dont use the -split " " in the $command variable, the echoargs is this:
Arg 0 is <-t VTL -p 5062 -m rpa-a-ap001 -s>
The script above may not be very dynamic, but the actual script im trying to make is going to be. The argumentlist should be configurable and dynamic, which is why im using the -f command on different occasions.
Can anyone see what I am doing wrong here and why the executable won't even acknowledge the existence of arguments?
Use the call operator (&) for running external commands. Use Join-Path for building paths. Use splatting for passing arguments to a program or function.
$omgeving = "A"
$exemap = "\\cfc01.corp\rpa-$omgeving\Scripts\Check_actualiteit_RPA" -f
$exe = "stmmon.exe"
$exepath = Join-Path $exemap $exe
$params = '-t', 'VTL',
'-p', 5062,
'-m', 'rpa-a-ap001',
'-s'
& $exepath #params
Also, judging from the help output, you seem to be missing a required parameter:
Usage: \\...\stmmon.exe [-v] [-h] -t <type> -p <port> -m <machine> -k <key> [-s]
If everything else fails, try this as a workaround:
$type = 'VTL'
$port = 5062
$server = 'rpa-a-ap001'
& cmd /c ('"{0}" -t {1} -p {2} -m {3} -s' -f $exepath, $type, $port, $server)
I am sorry to have wasted people's time here.
I have discovered the problem here. Apparently the 'type' arguments does not allow capital letters. I had not yet tried to change those, but it seems to have fixed the issue.

How to use both pipes and prevent shell expansion in perl system function?

If multiple arguments are passed to perl's system function then the shell expansion will not work:
# COMMAND
$ perl -e 'my $s="*"; system("echo", "$s" )'
# RESULT
*
If the command is passed as an one argument then the expansion will work:
# COMMAND
$ perl -e 'my $s="echo *"; system("$s")'
# RESULT
Desktop Documents Downloads
The system function also allows to using multiple commands and connect them using pipes. This only works when argument is passed as an one command:
# COMMAND
$ perl -e 'my $s="echo * | cat -n"; system("$s")'
# RESULT
1 Desktop Documents Downloads
How can I combine mentioned commands and use both pipes and prevent shell expansion?
I have tried:
# COMMAND
$ perl -e 'my $s="echo"; system("$s", "* | cat -n")'
# RESULT
* | cat -n
but this did not work because of reasons that I've described above (multiple arguments are not expanded). The result that I want is:
1 *
EDIT:
The problem that I'm actually facing is that when I use following command:
system("echo \"$email_message\" | mailx -s \"$email_subject\" $recipient");
Then the $email_message is expanded and it will break mailx if it contains some characters that are further expanded by shell.
system has three calling conventions:
system($SHELL_CMD)
system($PROG, #ARGS) # #ARGS>0
system( { $PROG } $NAME, #ARGS ) # #ARGS>=0
The first passes a command to the shell. It's equivalent to
system('/bin/sh', '-c', $SHELL_CMD)
The other two execute the program $PROG. system never prevents shell expansion or performs any escaping. There's simply no shell involved.
So your question is about building a shell command. If you were at the prompt, you might use
echo \* | cat -n
or
echo '*' | cat -n
to pass *. You need a function that performs the job of escaping * before interpolating it. Fortunately, one already exists: String::ShellQuote's shell_quote.
$ perl -e'
use String::ShellQuote qw( shell_quote );
my $s = "*";
my $cmd1 = shell_quote("printf", q{%s\n}, $s);
my $cmd2 = "cat -n";
my $cmd = "$cmd1 | $cmd2";
print("Executing <<$cmd>>\n");
system($cmd);
'
Executing <<printf '%s\n' '*' | cat -n>>
1 *
I used printf instead of echo since it's very hard to handle arguments starting with - in echo. Most programs accept -- to separate options from non-options, but not my echo.
All these complications beg the question: Why are you shelling out to send an email? It's usually much harder to handle errors from external programs than from libraries.
You can use open to pipe directly to mailx, without your content being interpreted by the shell:
open( my $mail, "|-", "mailx", "-s", $email_subject, $recipient );
say $mail $email_message;
close $mail;
More details can be found in open section of perlipc.

System command in perl

I need to run a system command which would go to a directory and delete sub directories excluding files if present. I wrote the below command to perform this operation:
system("cd /home/faizan/test/cache ; for i in *\; do if [ -d \"$i\" ]\; then echo \$i fi done");
The command above keeps throwing syntax error. I have tried multiple combinations but still not clear how this should go. Please suggest.
Well, your command line does contain syntax errors. Try this:
system("cd /home/faizan/test/cache ; for i in *; do if [ -d \"\$i\" ]; then echo \$i; fi; done");
Or better yet, only loop over directories in the first place;
system("for i in /home/faizan/test/cache/*/.; do echo \$i; done");
Or better yet, do it without a loop:
system("echo /home/faizan/test/cache/*/.");
(I suppose you will want to rmdir instead of echo once it is properly debugged.)
Or better yet, do it all in Perl. There is nothing here which requires system().
You're still best off trying this as a bash command first. Formatting that properly makes it much clearer that you're missing statement terminators:
for i in *; do
if [ -d "$i" ]; then
echo $i
fi
done
And condensing that by replacing new lines with semicolons (apart from after do/then):
for i in *; do if [ -d "$i" ]; then echo $i; fi; done
Or as has been mentioned, just do it in Perl (I haven't tested this to the point of actually uncommenting remove_tree - be careful!):
use strict;
use warnings;
use File::Path 'remove_tree';
use feature 'say';
chdir '/tmp';
opendir my $cache, '.';
while (my $item = readdir($cache)) {
if ($item !~ /^\.\.?$/ && -d $item) {
say "Deleting '$item'...";
# remove_tree($item);
}
}
Using system
my #args = ("cd /home/faizan/test/cache ; for i in *; do if [ -d \"\$i\" ]; then echo \$i; fi; done");
system(#args);
Using Subroutine
sub do_stuff {
my #args = ( "bash", "-c", shift );
system(#args);
}
do_stuff("cd /home/faizan/test/cache ; for i in *; do if [ -d \"\$i\" ]; then echo \$i; fi; done");
As question title stand for system command, this will answer directly, but the sample command using bash contain only thing that will be simplier in perl only (take a look at other answer using opendir and -d in perl).
If you want to use system (instead of open $cmdHandle,"bash -c ... |"), the prefered syntax for execution commands like system or exec, is to let perl parsing the command line.
Try this (as you've already done):
perl -e 'system("bash -c \"echo hello world\"")'
hello world
perl -e 'system "bash -c \"echo hello world\"";'
hello world
And now better, same but letting perl ensure command line parsing, try this:
perl -e 'system "bash","-c","echo hello world";'
hello world
There are clearly 3 argument of system command:
bash
-c
the script
or little more:
perl -e 'system "bash","-c","echo hello world;date +\"Now it is %T\";";'
hello world
Now it is 11:43:44
as you can see in last purpose, there is no double double-quotes enclosing bash script part of command line.
**Nota: on command line, using perl -e '...' or perl -e "...", it's a little heavy to play with quotes and double-quotes. In a script, you could mix them:
system 'bash','-c','for ((i=10;i--;));do printf "Number: %2d\n" $i;done';
or even:
system 'bash','-c','for ((i=10;i--;));do'."\n".
'printf "Number: %2d\n" $i'."\n".
'done';
Using dots . for concatening part of (script part) string, there are always 3 arguments.

help porting unix commands to perl script

I am getting some perl compile errors trying to convert these unix commands to perl.
The use of single quotes and double quotes is throwing me off (see below: my $curlcmd).
Here's the working unix commands executed in order:
export CERT=`/dev/bin/util --show dev.testaccnt | awk '{print $2}'`
/usr/bin/curl -c /home/foobar/cookee.txt --certify /dev/key.crt \
--header "FooBar-Util:'${CERT}'" \
https://devhost.foobar.com:4443/fs/workflow/data/source/productname?val=Summ
I want to do the same within Perl:
#Build cmd in perl
my $cookie='/home/foobar/cookee.txt';
my $certkey='/dev/key.crt';
my $fsProxyHostPort='devhost.foobar.com:4443';
my $fsPath='workflow/data/source/productname';
my $fsProxyOperation='Summ';
my $fsProxyURL="https://$fsProxyHostPort/fs/$fsPath?val=$fsProxyOperation";
#Get cert
my $cert=qx(/dev/bin/pass-util --show foobar.dev.testaccnt | awk '{print \$2}');
Here's where I am having trouble executing it:
my $curlcmd = qx(/usr/bin/curl -c $cookie --certify $certkey --header "FooBar-Util:'${" . $cert . "}'". $fsProxyURL);
Can someone show me how to setup these commands in Perl correctly?
In the shell script, you have (in part):
--header "FooBar-Util:'${CERT}'"
This generates something like:
--header FooBar-Util:'data-from-certificate'
where the curl command gets to see those single quotes. To get the same result in Perl, you will need:
my $header = "FooBar-Util:'$cert'";
my $out = qx(/usr/bin/curl -c $cookie --certify $certkey --header $header $fsProxyURL);
Changes:
Lost the ${ ... } notation.
Lost the concatenation operations.
In situations where you have problems seeing the argument list sent to a command, I recommend using a program analogous to the shell echo command, but which lists each argument on its own line, rather than as a space-separated set of arguments on a single line. I call my version of this al for 'argument list'. If you test your commands (for example, the shell version) by prefixing the whole command line with al, you get to see the arguments that curl would see. You can then do the same in Perl to compare the arguments curl sees at the shell with the ones given it by Perl. Then you can fix the problems, typically much more easily.
For debugging with al:
my #lines = qx(al /usr/bin/curl -c $cookie --certify $certkey --header $header $fsProxyURL);
foreach my $line (#lines) { print "$line"; }
If you want to write al in Perl:
#!/usr/bin/env perl
foreach my $arg (#ARGV) { print "$arg\n"; }
Adventures in Validating an Answer
Fortunately, I usually check what I write as answers - and what is written above is mostly accurate, except for one detail; Perl manages to invoke the shell on the command, and in doing so, the shell cleans out the single-quotes:
my $cert = 'certificate-info';
my $fsProxyURL = 'https://www.example.com/fsProxy';
my $cookie = 'cookie';
my $certkey = 'cert-key';
my $header = "FooBar-Util:'$cert'";
#my #out = qx(al /usr/bin/curl -c $cookie --certify $certkey --header $header $fsProxyURL);
my #cmdargs = ( 'al', '/usr/bin/curl', '-c', $cookie, '--certify', $certkey, '--header', $header, $fsProxyURL);
print "System:\n";
system #cmdargs;
print "\nQX command:\n";
my #lines = qx(#cmdargs);
foreach my $line (#lines) { print "$line"; }
This yields:
System:
/usr/bin/curl
-c
cookie
--certify
cert-key
--header
FooBar-Util:'certificate-info'
https://www.example.com/fsProxy
QX command:
/usr/bin/curl
-c
cookie
--certify
cert-key
--header
FooBar-Util:certificate-info
https://www.example.com/fsProxy
Note the difference in the `FooBar lines!
At this point, you start to wonder what's the least unclean way to work around this. If you want to use the qx// operator, then you probably do:
my $header = "FooBar-Util:\\'$cert\\'";
This yields the variant outputs (system then qx//):
FooBar-Util:\'certificate-info\'
FooBar-Util:'certificate-info'
So, the qx// notation now gives the single quotes to the executed command, just as in the shell script. This meets the goal, so I'm going to suggest that it is 'OK' for you; I'm just not sure that I'd actually adopt it in my own code, but I don't have a cleaner mechanism on hand. I'd like to be able to use the system plus 'array of arguments' mechanism, while still capturing the output, but I haven't checked whether there's a sensible (meaning relatively easy) way to do that.
One other passing comment; if any of your arguments contained spaces, you'd have to be excruciatingly careful with what gets passed through the shell. Having the al command available really pays off then. You can't identify which spaces in echo output are parts of one argument and which are separators provided by echo.
Is it on purpose that you have two different definitions of $cert?
Your translation of --header "FooBar-Util:'${CERT}'" is bad. The ${...} tells the shell to insert the CERT variable, but since you're already doing this insertation from Perl, it is not needed and will just confuse.
You're also missing a space before the $fsProxyURL.
As you're apparently not using the captured output from curl for anyting, I would suggest that you use the system function instead so you avoid the use of an intermediate shell command line parsing:
system "/usr/bin/curl","-c",$cookie,"--certify",$certTheFirst,
"--header","FooBar-Util:'$certTheSecond'", $fsProxyURL;
Finally, it's not very perlish to use a subsidiary awk to split the pass-util value into fields when Perl does that kind of things perfectly well. Once you solve the immediate error, I suggest
my #passwordline = split / /, qx(/dev/bin/util --show dev.testaccnt);
my $certTheSecond = $passwordline[1];
This " . $cert . " seems to be a leftover from some other code, where black-quotes were used and not qx. Removing the black-quotes and concatenations (.) works on my machine.
So, to execute the command, do:
my $curlcmd = qx(/usr/bin/curl -c $cookie --certify $certkey --header "FooBar-Util:'${ $cert }'". $fsProxyURL);
Following your comment, if you just want to print the command, you can do:
my $curlcmd = qq(/usr/bin/curl -c $cookie --certify $certkey --header "FooBar-Util:'${ $cert }'". $fsProxyURL);
print $curlcmd;