Perl Read YAML from a variable to hash - perl

I have a linux command which fetches some data in yaml format from an api. I was looking for a way to populate this variable to a hash array. I was trying YAML::Tiny and the chunk of code that I wrote was
use YAML::Tiny;
my $stuff = `unix command that returns as yaml output`
my $yaml = YAML::Tiny->new;
$yaml = YAML::Tiny->new->read_string->($stuff);
but running with this code will error out as
Can't use string ("") as a subroutine ref while "strict refs" in use
$stuff variable would look like
Cluster1:
source_mount: /mnt/uploads
dir: /a /b
default: yes
destination_mount: /var/pub
Cluster2:
source_mount: /mnt/uploads
dir: /c /d /e
default: no
destination_mount: /var/pub

You should not use new twice, and there should not be -> after read_string (refer to the POD for YAML::Tiny). Change:
$yaml = YAML::Tiny->new->read_string->($stuff);
to:
$yaml = YAML::Tiny->read_string($stuff);
Here is a complete working example:
use warnings;
use strict;
use YAML::Tiny;
my $stuff = '
Cluster1:
source_mount: /mnt/uploads
dir: /a /b
default: yes
destination_mount: /var/pub
Cluster2:
source_mount: /mnt/uploads
dir: /c /d /e
default: no
destination_mount: /var/pub
';
my $yaml = YAML::Tiny->new();
$yaml = YAML::Tiny->read_string($stuff);
print $yaml->[0]->{Cluster2}->{dir}, "\n";
__END__
/c /d /e

Related

how to run a shell command in perl in backticks when command also has backticks

example.
$output= `eval `environment` ; echo $Variable` ;
i want to execute this command
eval `environment` ; echo $Variable
in a perl script.
$output= `eval `environment` ; echo $Variable` ;
Use the qx// form from the "Quote-like Operators":
my $output = qx{eval `environment` ; echo $Variable};
But that still probably wouldn't do what you want, since $Variable would be already evaluated and interpolated by Perl. To fix that:
# single quotes:
my $command = q{eval `environment` ; echo $Variable};
# and then execute it:
my $output = qx{$command};
Alternatively, a little hack:
my $output = qx'eval `environment` ; echo $Variable';
When the ' is used as the character to the quote-like operators, it inhibits the variable interpolation.
You may use Backtiks "here documents" - see Backtics sub section in man perlop for details. $ sign must be escaped if you want to avoid variable expansion by perl.
#!/usr/bin/perl
use strict;
use warnings;
my $output = <<`END`;
eval `environment` ; echo \$Variable
END
print $output;

How to launch many programs sequentially without being block?

In unix system
I have a directory called program_sets , and in the program_sets , there exists 8 directory and in each directory they have a program called A.pl
I want to launch and run 8 A.pl programs in the same time , but when I launch the first program , the procedure will be block until the first program call is finish . How can I solve this problems?
here is my code
#!/usr/bin/perl
opendir(Programs,"./program_sets");
#Each_names = readdir(Programs);
shift(#Each_names);
shift(#Each_names);
for($i=0;$i<=$#Each_names;$i++)
{
`perl ./program_sets/$Each_names[$i]/A.pl`;
}
thanks
Run them in the background with &, just like you would from the shell.
for($i=0;$i<=$#Each_names;$i++)
{
system("perl ./program_sets/$Each_names[$i]/A.pl >/dev/null 2>&1 &");
}
Also, backticks should be used when you're assigning the output to a variable. Use system() to run a command without saving the output.
In *NIX, you can add "&" to the command-line to launch the programs in the background.
Another option is to use fork() http://perldoc.perl.org/functions/fork.html
There looks to be some other issues here as well.
#!/usr/bin/perl
# warnings, strict
use warnings;
use strict;
# lexically scoped $dh
#opendir(Programs,"./program_sets");
my $cur_dir = "./program_sets";
opendir(my $dh, $cur_dir);
# what exactly is being shifted off here? "." and ".."??
##Each_names = readdir(Programs);
#shift(#Each_names);
#shift(#Each_names);
# I would replace these three lines with a grep and a meaningful name.
# -d: only directories. /^\./: Anything that begins with a "."
# eg. hidden files, "." and ".."
my #dirs = grep{ -d && $_ !~ /^\./ } readdir $dh;
close $dh;
for my $dir ( #dirs ) {
my $path = "$cur_dir/$dir";
system("perl $path/A.pl >/dev/null 2>&1 &");
}

Suppress prints from TASKKILL

I'm writing a Perl script that makes system calls to kill running processes. For instance, I want to kill all PuTTy windows. In order to do this, I have:
system('TASKKILL /F /IM putty* /T 2>nul');
For each process that's killed, however, I get a print saying
SUCCESS: The process with PID xxxx child of PID xxxx has been terminated.
which is cluttering my CLI. What's an easy way to eliminate these prints? Also note, that I'm executing these scripts in Cygwin.
Redirect sderr->stdout->nul:
system('TASKKILL /F /IM putty* /T 1>nul 2>&1');
or just simply grab output:
my $res = `TASKKILL /F /IM putty* /T 2>nul`;
TASKKILL writes to the first file descriptor (standard output), not the second.
You want to say
system('TASKKILL /F /IM putty* /T >nul');
$exec_shell='TASKKILL /F /IM putty* /T 2>nul';
my $a = run_shell($exec_shell);
#i use this function:
sub run_shell {
my ($cmd) = #_;
use IPC::Open3 'open3';
use Carp;
use English qw(-no_match_vars);
my #args = ();
my $EMPTY = q{};
my $ret = undef;
my ( $HIS_IN, $HIS_OUT, $HIS_ERR ) = ( $EMPTY, $EMPTY, $EMPTY );
my $childpid = open3( $HIS_IN, $HIS_OUT, $HIS_ERR, $cmd, #args );
$ret = print {$HIS_IN} "stuff\n";
close $HIS_IN or croak "unable to close: $HIS_IN $ERRNO";
; # Give end of file to kid.
if ($HIS_OUT) {
my #outlines = <$HIS_OUT>; # Read till EOF.
$ret = print " STDOUT:\n", #outlines, "\n";
}
if ($HIS_ERR) {
my #errlines = <$HIS_ERR>; # XXX: block potential if massive
$ret = print " STDERR:\n", #errlines, "\n";
}
close $HIS_OUT or croak "unable to close: $HIS_OUT $ERRNO";
#close $HIS_ERR or croak "unable to close: $HIS_ERR $ERRNO";#bad..todo
waitpid $childpid, 0;
if ($CHILD_ERROR) {
$ret = print "That child exited with wait status of $CHILD_ERROR\n";
}
return 1;
}

How do I tell perl to execute a piece of generated Perl code?

I am trying to catch the string cd /a/b/c and do the following conversion
(as part of a larger Perl program).
If cd /a/b/c exists then convert cd /a/b/c → chdir '/a/b/c' and execute chdir '/a/b/c'
I can do the conversion; I can't tell perl to execute my command.
#!/usr/bin/perl
use strict; use warnings;
while ( my $line = <DATA> ) {
if ( my ($path) = $line =~ m{^cd \s+ (/? (\w+) (?:/\w+)* )}x ) {
warn "Path is $path\n";
chdir $path
or warn "Cannot chdir to '$path': $!";
}
}
__DATA__
cd a
cd /a/b/c
cd /a
Output:
Path is a
Cannot chdir to 'a': No such file or directory at C:\Temp\k.pl line 8, line 1.
Path is /a/b/c
Cannot chdir to '/a/b/c': No such file or directory at C:\Temp\k.pl line 8, line 2.
Path is /a
Cannot chdir to '/a': No such file or directory at C:\Temp\k.pl line 8, line 3.
What you really want is a dispatch table. When you encounter a command, like cd, you look up an associated subroutine in the dispatch table where you map valid commands to the code that you want to run:
%dispatch = (
cd => sub { chdir( $_[0] ) },
...
);
while( <> )
{
my( $command, #args ) = split;
if( exists $dispatch{ $command } )
{
$dispatch{ $command }->(#args);
}
}
I have several extended examples of this sort of thing in Mastering Perl. The nice bit about this is that you don't change the processing loop when you have new commands, and you only handle the commands you intend to handle. Furthermore, you can construct that dispatch table directly from configuration.
if the directory you want to find is known beforehand.
$str = "blah blah cd /a/b/c blah";
if ( $str =~ /cd \/a\/b\/c/ ){
print "found\n";
chdir("/a/b/c");
}

Why aren't a batch file's environment variables set when run from Perl?

I am trying to invoke a bat file from a perl script as follows:
system("call D:/SIP/run_$file_idx.bat");
However I observe that the environment variables in the bat fail to get resolved.
If I run the bat file separately from the command prompt it works.
Does system() create a new environment and execute bat file in that?
What is that I am missing?
if (($ENV{'IPSL_RUN_FLAG'}) eq "TRUE") {
my $Book = $Excel->Workbooks->Open(
"$ENV{'IPSL_TESTCASES_PATH'}IPSLFeatureLoadRunResults.xls");
# Make transparent where the IPSL tarball is installed.
# Have ControlPanel save results here.
# You can dynamically obtain the number of worksheets, rows, and columns
# through the Excel OLE interface. Excel's Visual Basic Editor has more
# information on the Excel OLE interface. Here we just use the first
# worksheet, rows 1 through 4 and columns 1 through 3.
# select worksheet number 1 (you can also select a worksheet by name)
my $count=0;
my $Sheet = $Book->Worksheets("LOADDATA");
my $tmp=0;
foreach my $row (13..776) {
foreach my $col (17..17) {
if(($Sheet->Cells($row,$col)->{'Value'} eq "Failed") ||
($Sheet->Cells($row,$col)->{'Value'} eq "No Run") ) {
$cnt_of_current_rerun_testcases++;
foreach my $col (18..18) {
# skip empty cells
next unless defined $Sheet->Cells($row,$col)->{'Value'};
my $a = $Sheet->Cells($row,$col)->{'Value'};
my $i = index($a, 'run');
$a = substr($a, 0, $i); #remove runTest*
print OUT "\n";
if($count == 0) {
print OUT "\nREM ";
print OUT "*" x 100;
print OUT "\n";
print OUT "\ntaskkill /F /FI \"USERNAME eq %USERNAME%\" /IM ips32.exe";
print OUT "\ntaskkill /F /FI \"USERNAME eq %USERNAME%\" /IM ipsldb.exe";
print OUT "\ntaskkill /F /FI \"USERNAME eq %USERNAME%\" /IM ipsltiu.ex\n";
}
print OUT "c:\n";
print OUT "\ncd ";
$a =~ s/%I/\"%I/g;
$a=~s/H%/H%\"/g;
print OUT " $a\n";
print OUT "\n";
print OUT "CALL run_SubscribeFeatureOnHIQ.bat";
print OUT "\n";
print OUT "sleep 10\n";
print OUT "\ncd ";
print OUT " $a\n";
print OUT "\n";
print OUT "CALL ";
$i=$Sheet->Cells($row,$col)->{'Value'};
$i=~ s/%I/\"%I/g;
$i=~s/H%/H%\"/g;
print OUT $i;
#print OUT $Sheet->Cells($row,$col)->{'Value'};
print OUT "\n";
$count++;
if($count == $no_simul_tcases) {
$sleep_cnt++;
print OUT "echo Going for sleep $sleep_cnt\n";
print OUT "SLEEP 300";
print OUT "\n";
$count=0;
}
}
}
}
}
print OUT "\ntaskkill /F /FI \"USERNAME eq %USERNAME%\" /IM ips32.exe";
print OUT "\ntaskkill /F /FI \"USERNAME eq %USERNAME%\" /IM ipsldb.exe";
print OUT "\ntaskkill /F /FI \"USERNAME eq %USERNAME%\" /IM ipsltiu.ex\n";
print OUT "\nset IPSL_RUN_FLAG=TRUE";
close OUT ;
system(\"start $ENV{'IPSL_TESTCASES_PATH'}SIP\\run_$file_idx.bat\");
And the batch file is:
taskkill /F /FI "USERNAME eq %USERNAME%" /IM ips32.exe
taskkill /F /FI "USERNAME eq %USERNAME%" /IM ipsldb.exe
taskkill /F /FI "USERNAME eq %USERNAME%" /IM ipsltiu.ex
c:
cd "%IPSL_TESTCASES_PATH%"SIP\TestCASE\FEATURESINT\INT_cfSRS\INT_cfSRS_cfSERRNG\
CALL run_SubscribeFeatureOnHIQ.bat
sleep 10
cd "%IPSL_TESTCASES_PATH%"SIP\TestCASE\FEATURESINT\INT_cfSRS\INT_cfSRS_cfSERRNG\
CALL "%IPSL_TESTCASES_PATH%"SIP\TestCASE\FEATURESINT\INT_cfSRS\INT_cfSRS_cfSERRNG\runTest_SRS_INT_SERRNG.bat
Possible workaround:
By using exec and specifically writing the ENV into bat file gave me a workaround:
print OUT map { "set $_=$ENV{$_}\n" }
qw( path USERPROFILE USERNAME ProgramFiles ComSpec APPDATA );
The issue is still seen with system() though. I tried Sinan's suggestion in the answerers..
Child processes inherit the environment of their parent. The bat file should have the same environment as the Perl script. If you haven't setup the environment correctly, your bat file won't see it.
Can you expand your answer to show your test case, similar to what Sinan has done? Although you say in your comment that the code is simple, that is never true. Show us the complete Perl program you have and the complete bat file that you have. Don't make us guess or do a lot of work to figure out what you are doing.
Update: Now that you've provided some code, start cutting parts out of it to make the simplest test case that still shows the problem. When you are investigating these sorts of problems, stop working on the big script and write a little script that exercises the problem you think you're having. That way, you isolate it from everything else you might be messing up.
Update 2: Let's look more closely at your system call:
system(\"start $ENV{'IPSL_TESTCASES_PATH'}SIP\\run_$file_idx.bat\");
That should be syntax error and your script should not even be able to invoke the batch file. Try
system(qq{start $ENV{IPSL_TESTCASES_PATH}SIP\\run_$file_idx.bat\\});
Update 1: Looking at the code you posted, you are using start in the system call. That starts a new cmd.exe process and that cmd.exe will remain long after your script has terminated. However, I still do not observe behavior as you describe and I am going to look at your code dump more closely.
C:\Temp> cat bg.bat
#echo %YAEV%
#echo %*
C:\Temp> cat t.pl
#!/usr/local/bin/perl
use strict;
use warnings;
$ENV{YAEV} = 'Yet another environment variable';
system 'start bg.bat "This is a test"';
In a new cmd.exe window:
C:\Temp> t
Yet another environment variable
"This is a test"
The system command [contested: /function] will start your batch script in the default batch file context (which is the system-wide environment), if your parameter to the system call is a scalar. It is basically the same as starting the command using "cmd /c myfile.bat". If your argument to system() is an array [contested: /list], you will not have this problem. Please read this for more information.