Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 months ago.
Improve this question
#edit #Clarity
Objective: API Testing
Functionality: Reads the API.json and parses it. User inputs a key and gets the value.
Problem: The value for the key active equals "true" or "false", script prints "1" or " ".
Code:
use strict;
use warnings;
use JSON;
use JSON::Parse 'parse_json';
print "Loading API.\n";
my $json = `curl -k --silent -u admin:pass https://url...`;
print "API is loaded.\n";
my $decoded_json = parse_json ($json);
my $input = <STDIN>;
chomp $input;
my ($item, $mac);
foreach $item (#$decoded_json) {
my $mac = $item->{address};
if ($input eq "active") {
#$value = $item->{active};
if ($value eq 1){
print ("Device with the MAC-Adress: $mac is active!\n");
}elsif($value eq 0){
print ("Device with the MAC-Adress: $mac is disabled!\n");
}else{
print ("Device with the MAC-Adress: $mac, $input is: ", $item->{$input}, "\n");
}
};
Output when user asks for id:
Device with MAC-Adress: xx:xx:xx:xx:xx:xx, id is: 6
Output when user asks for device status:
Device with the MAC-Adress: xx:xx:xx:xx:xx:xx, active is: 1
or
Device with the MAC-Adress: xx:xx:xx:xx:xx:xx, active is:
Target: True value prints active and false value prints false.
The script obviously skips the first two ifs, and jumps to the third option and I canĀ“t figure out why... There are no errors shown terminal.
After many hours of rewriting, works as intended. Solution below
foreach $item (#$decoded_json) {
my $mac = $item->{address};
if ($input eq "active") {
$value = $item->{active};
if ($value eq 1){
print ("Device with the MAC-Adress: $mac is active!\n");
}elsif($value eq ""){
print ("Device with the MAC-Adress: $mac is disabled!\n");
}}else{
print ("Device with the MAC-Adress: $mac, the value of $input is: ", $item->{$input}, "\n");
}
};
I say you need to figure out where in your data structure you need to add the parameter.
Here is a example using this sample json API
https://www.appsloveworld.com/free-online-sample-rest-api-url-for-testing
Where I use Data::Dumper to look at the data structure then loop thru the structure adding active = true and then dump the structure again:
#!/usr/bin/env perl
use strict;
use warnings;
use JSON;
use JSON::Parse 'parse_json';
use Data::Dumper;
print "Loading API.\n";
# here is a test API we can access for testing
my $json = `curl -k --silent http://restapi.adequateshop.com/api/Tourist?page=1`;
print "API is loaded.\n";
my $decoded_json = parse_json ($json);
print "first use Data Dumper to examine data structure:\n";
print Dumper $decoded_json;
print "\n\nNext create whatever loops you need to access the data, it appears with JSON::Parse the data is in the hash 'data'\n";
foreach my $item (# {$decoded_json->{data}} ) {
$item->{active} = 'true' if ($item);
foreach my $key ( keys %{$item} ) {
print "$key => $item->{$key} \n";
}
}
print "\n\nlast use Data Dumper to examine data structure again:\n";
print Dumper $decoded_json;
Related
This question already has an answer here:
Regex $1 into variable interferes with another variable
(1 answer)
Closed 5 years ago.
I want the following code to print out "bye", and then print out "hello". However, when I run it, it prints out "bye" and then perl tells me that $str2 has not been initialized.
my $item = "hello/bye";
if($item =~ m/.*(bye)/g){
my $str1 = $1;
print "$str1\n";
my $str2 = ($item =~ m/(hello).*/g)[0];
print "$str2\n";
}
I think that there is probably something I do not understand about the m//g part, but I am having trouble finding my answer in the perldoc page for perlre.
When you do
if($item =~ m/.*(bye)/g)
that does not reset the match iterator (we are in scalar context). The "position" remains at the character after the bye substring. So the following m//g picks up from there the previous one left off.
You can verify this yourself:
if ($item =~ /(bye)/g) {
printf "pos \$item = %d\n", pos $item;
...
}
which will print pos $item =9.
Incidentally $item =~ /.*(bye)/ is better written as $item =~ /(bye)/ (assuming you don't care if you match the first or the last bye substring, just that $item has bye somewhere). Similarly, $item =~ /(hello).*/ is better written as $item =~ /(hello)/.
#!/usr/bin/env perl
use strict;
use warnings;
my $item = "hello/bye";
if ($item =~ /(bye)/) {
my $str1 = $1;
print "$str1\n";
my $str2 = ($item =~ /(hello)/g)[0];
print "$str2\n";
}
In my below program, I was trying to search a string from no of files In a folder but output Is printing in continuous manner rather than stopping after required search. Can some one pls help to point out the error ?
i.e. I am trying to Search the string "VoLTE SIPTX: [SIPTX-SIP] ==> REGISTER" from #files but I am not getting the desired output but I am getting repetitive output of my strings.
# #!/usr/bin/perl
# use strict;
use warnings;
&IMS_Compare_Message();
sub IMS_Compare_Message
{
print "Entering the value i.e. the IMS Message to compare with";
my $value = '';
my $choice = '';
my $loop = '';
print "\nThe script path & name is $0\n";
print "\nPlease enter desired number to select any of the following
(1) Start Comparing REGISTER message !!
(2) Start Comparing SUBSCRIBE message
(3) Start Comparing INVITE message \n";
$value = <STDIN>;
if ($value == 1 )
{
print "\n Start Comparing REGISTER message\n\n";
$IMS_Message = "VoLTE SIPTX: [SIPTX-SIP] ==> REGISTER";
#chomp ($IMS_Message);
}
elsif ($value == 2)
{
print "\n SUBSCRIBE message Flow\n\n";
}
elsif ($value == 3)
{
print "\n INVITE message Flow\n\n";
}
else
{
print "\nThe input is not valid!\n";
print "\nDo you want to continue selecting a Automation Mode again (Y or N)?\n";
$choice = <STDIN>;
if( $choice =~ /[Yy]/) {
test_loop();
} else {
exit;
}
}
my $kw = "$IMS_Message";
my #files = grep {-f} (<*main_log>);
foreach my $file (#files)
{
open(my $fh, '<', $file) or die $!;
my #content = <$fh>;
close($fh);
my $l = 0;
$search = chomp ($kw);
#my $search = quotemeta($kw);
foreach (#content)
{ # go through every line for this keyword
$l++;
if (/$search/)
{
printf 'Found keyword %s in file %s, line %d:%s'.$/, $kw, $file, $l, $_
}
}
}
}
After Modificaiton
# #!/usr/bin/perl
use strict;
use warnings;
print "Entering the value i.e. the IMS Message to compare with";
my $value = '';
my $choice = '';
my $loop = '';
my $IMS_Message = '';
my $search = '';
my $kw = '';
print "\nThe script path & name is $0\n";
print "\nPlease enter desired number to select any of the following
(1) Start Comparing REGISTER message !!
(2) Start Comparing SUBSCRIBE message
(3) Start Comparing INVITE message \n";
$value = <STDIN>;
if ($value == 1 )
{
print "\n Start Comparing REGISTER message\n\n";
$IMS_Message = "VoLTE SIPTX: [SIPTX-SIP] ==> REGISTER";
#chomp ($IMS_Message);
}
elsif ($value == 2)
{
print "\n SUBSCRIBE message Flow\n\n";
}
elsif ($value == 3)
{
print "\n INVITE message Flow\n\n";
}
else
{
print "\nThe input is not valid!\n";
print "\nDo you want to continue selecting a Automation Mode again (Y or N)?\n";
$choice = <STDIN>;
if( $choice eq /[Yy]/) {
test_loop();
} else {
exit;
}
$kw = $IMS_Message;
$search = qr/\Q$kw/;
for my $file ( grep { -f } glob '*main_log' ) {
open my $fh, '<', $file or die qq{Unable to open "$file" for input: $!};
while ( <$fh> ) {
if ( /$search/ ) {
printf "Found keyword %s in file %s, line %d: %s\n", $kw, $file, $., $_;
last;
}
}
}
}
Here are some observations on your code
Your approach to debugging appears to be to try things at random to see if they work. It would be far more fruitful to add diagnostic print statements so that you can compare variables' actual values with what you expect
Error and warning messages are useful information, and it is foolish to comment out use strict to make them go away
Don't call subroutines with an ampersand &. That hasn't been best practice for twenty years now
Lay your code out tidily and cinsistently, so that both you and any people you ask for help can read it easily. As it stands it is impossible to tell where blocks start and end without counting brace characters {...}
Variables should be declared with my as close as possible to their first point of use, and not all at once at the top of the file or subroutine
chomp is necessary only for strings that have been read from the terminal or from a file. It returns the number of characters removed, not the trimmed string
if( $choice =~ /[Yy]/ ) { ... } will check only whether the string contains a Y, so if the operator enters MARRY ME! it will return true. You should use string equality eq to check whether a single Y character has been typed
You shouldn't put scalar variables alone inside double quotes. At best it will make no difference, and just add noise to your code; at worst it will completely change the value of the variable. Just my $kw = $IMS_Message is correct
Unless you require non-sequential access to the contents of a file, it is best to use a while loop to read and process it line by line, rather than read the whole thing into an array and process each element of the array. This also allows you to use the built-in line number variable $. instead of implementing your own $l
The main problem is that you have derived $search from the result of chomp $kw, which sets $search to the number of characters removed by chomp. This is always zero because $kw is a copy of $IMS_Message, which has no newline at the end. That means you are checking all the lines of every file for the character 0, and not for the message that you intended. The correct way is my $search = quotemeta($kw) which you had in place but have commented out, presumably as a result of your policy of "debugging by guesswork"
Fixing these things, your code should look something like this
my $search = qr/\Q$kw/;
for my $file ( grep { -f } glob '*main_log' ) {
open my $fh, '<', $file or die qq{Unable to open "$file" for input: $!};
while ( <$fh> ) {
if ( /$search/ ) {
printf "Found keyword %s in file %s, line %d: %s\n", $kw, $file, $., $_;
last;
}
}
}
I want to ask the user multiple questions. I have two types of questions: Y/N or filename input. I'm not sure how to place this all into a nice if structure. And I'm not sure if I should use 'else' statements too. Could someone help we with this? This is what I have so far:
print "Do you want to import a list (Y/N)?"; # first question yes/no
my $input = <STDIN>;
chomp $input;
if ($input =~ m/^[Y]$/i){ #match Y or y
print "Give the name of the first list file:\n";
my $list1 = <STDIN>;
chomp $list1;
print "Do you want to import another gene list file (Y/N)?";
if ($input =~ m/^[Y]$/i){
print "Give the name of the second list file:\n" # can I use $input or do I need to define another variable?;
$list2 = <STDIN>;
chomp $list2;
print "Do you want to import another gene list file (Y/N)?";
}
}
One word: Abstraction.
The solution you currently chose does not scale well, and contains too much repeated code. We will write a subroutine prompt that hides much of the complexity from us:
sub prompt {
my ($query) = #_; # take a prompt string as argument
local $| = 1; # activate autoflush to immediately show the prompt
print $query;
chomp(my $answer = <STDIN>);
return $answer;
}
And now a promt_yn that asks for confirmation:
sub prompt_yn {
my ($query) = #_;
my $answer = prompt("$query (Y/N): ");
return lc($answer) eq 'y';
}
We can now write your code in a way that actually works:
if (prompt_yn("Do you want to import a list")){
my $list1 = prompt("Give the name of the first list file:\n");
if (prompt_yn("Do you want to import another gene list file")){
my $list2 = prompt("Give the name of the second list file:\n");
# if (prompt_yn("Do you want to import another gene list file")){
# ...
}
}
Oh, so it seems you actually want a while loop:
if (prompt_yn("Do you want to import a list")){
my #list = prompt("Give the name of the first list file:\n");
while (prompt_yn("Do you want to import another gene list file")){
push #list, prompt("Give the name of the next list file:\n");
}
...; # do something with #list
}
The #list is an array. We can append elements via push.
A while ago I end up with following:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
if (&prompt_yn("CONTINUE")){
my #res = split(" ",&prompt("ENTER INPUT")) ;
print Dumper #res;
}
else{
print "EXIT\n";
}
sub prompt_yn{
my ($query) = #_;
$query = $query . " (Y/N): ";
print "$query";
while (<>) {
$_ =~ s/^\s+|\s+$//g;
$_ =~ s/\r|\n//g;
if ($_ =~ /\S/){
if ($_ =~ /^y$|^yes$/i){
# if you want information message about entered value uncomment this
# print "You have entered Y\n";
return 1;
}
elsif ($_ =~ /^n$|^no$/i){
# if you want information message about entered value uncomment this
# print "You have entered N\n";
return 0;
}
else{
# if you want information message about entered value uncomment this
# print "You have entered wrong value try again: \n";
}
}
print "$query";
}
}
sub prompt{
my ($query) = #_;
$query = $query . ": ";
print "$query";
while (<>) {
$_ =~ s/^\s+|\s+$//g;
$_ =~ s/\r|\n//g;
if ($_ =~ /\S/){
return $_;
}
print "$query";
}
}
Compared to previous solutions this handles empty inputs.
You can use Sub Routines.
This helps you visibly and logically keep everything in-line.
for instance
&main();
sub main {
print "Do you want to import a list(Y/N)";
my $input = ;
chomp $input;
if($input =~ m/^[Y]$/i) {
&importfile();
} elsif ($input =~ m/^[N]$/i) {
print "you said no";
} else {
print "Invalid option";
}
}
sub importfile
{
print "file name please ";
my $file = STDIN;
# import and process the file here.....
&main();
}
So you can import at many files this way.
This script works on localhost with the -w switch but not without. It also works when use strict and use warning are active.
apache2/error.log:
without switch (aborted script):
(2)No such file or directory: exec of ... failed
with the switch I get:
Use of uninitialized value $email_flag in string ne ...
which looks initialised to me.
On the live web server neither one works. Perl is new to me, but I know some BASH and PHP.
I run Debian Lenny, Apache2, Perl 5.10.
#!/usr/bin/perl -w
$| = 1;
my $mailprog = '/usr/sbin/sendmail'; # where the mail program lives
my $to = "not\#for.you"; # where the mail is sent
my ($command,$email,#pairs,$buffer,$pair,$email_flag) ;
read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
#pairs = split(/&/, $buffer);
foreach $pair (#pairs) {
# Split the pair up into individual variables. #
my($name, $value) = split(/=/, $pair);
# Decode the form encoding on the name and value variables. #
$name =~ tr/+/ /;
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
# If they try to include server side includes, erase them, so they
# aren't a security risk if the html gets returned. Another
# security hole plugged up.
$value =~ s/<!--(.|\n)*-->//g;
## print "Name of form element is $name with value of $value \n";
if ($name eq 'email') {
$email = $value;
}
if ($name eq 'command') {
$command = $value;
}
}
if ($email =~ /(#.*#)|(\.\.)|(#\.)|(\.#)|(^\.)/ ||
$email !~ /^.+\#(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,3}|[0-9]{1,3})(\]?)$/ ) {
$email_flag = "ERROR";
}
my $urlcommand = $command;
if ($command eq 'Subscribe') {
$command = "SUBSCRIBE rpc-news";
}
if ($command eq 'Unsubscribe') {
$command = "UNSUBSCRIBE rpc-news";
}
if ($command eq 'Suspend') {
$command = "SET rpc-news NOMAIL";
}
if ($command eq 'Resume') {
$command = "SET rpc-news MAIL";
}
my $getInfo = '';
print "Content-Type: text/html\n";
if ($email_flag ne "ERROR") {
open(MAIL,"|$mailprog -t");
print MAIL "To: $to\n";
print MAIL "From: $email\n";
print MAIL "Subject: [rpc-news] $command \n";
print MAIL "Reply-to: $email \n";
print MAIL "$command \n";
print MAIL "EXIT \n";
close (MAIL);
$getInfo = "?result=good";
}
if ($email_flag eq "ERROR") {
$getInfo = "?result=bad";
}
my $rootURL= $ENV{'SERVER_NAME'};
my $url = "http://${rootURL}/thank_you.html${getInfo}&action=${urlcommand}";
print "Location: $url\n\n";
Did you create your script on a Windows machine and upload it to a Linux server without fixing the line endings? Without the -w switch, the shebang line may look like "#!/usr/bin/perl\r", so the system goes looking for a program named "perl\r" (or however the line ending looks). With the -w switch, "#!/usr/bin/perl" doesn't have an indecipherable line ending stuck to it. Instead, that gets stuck to -w where it doesn't cause failure.
I thought there was a perlfaq about this, but I can't seem to find it in the docs at the moment.
Update: I found it over on PerlMonks, in a really old Q&A topic that seems unrelated until you read the body of the message: Answer: How to get rid of premature end of script headers. Yeah, I know, if you were just browsing threads you wouldn't even stop on that one. But here's the text of the post:
If you developed this script on
Windows, it's possible that the script
file has non-UNIX line endings. (The
perl interpreter can handle them, but
the shebang line is interpreted by the
shell, and is not tolerant of
incorrect line endings.) If this is
the problem, the script may terminate
with an error right at the shebang
line.
Use of uninitialized value $email_flag in string ne ...
which looks initialised to me.
if ($email =~ /(#.*#)|(\.\.)|(#\.)|(\.#)|(^\.)/ ||
$email !~ /^.+\#(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,3}|[0-9]{1,3})(\]?)$/
) {
$email_flag = "ERROR";
}
$email_flag only gets initialized here if the pattern matches - otherwise it's left undefined. You could add an else clause to ensure it gets initialized no matter what.
I would not use that code, it doesn't use CGI.pm (or CGI::Simple ...)
Get "TFMail -- Improved Form Mail" from "nms - web programs written by experts"
Its simple to install, and its written well ( it uses CGI ...)
The following code generates a list of the average number of clients connected by subnet. Currently I have to pipe it through sort | uniq | grep -v HASH.
Trying to keep it all in Perl, this doesn't work:
foreach $subnet (keys %{keys %{keys %days}}) {
print "$subnet\n";
}
The source is this:
foreach $file (#ARGV) {
open(FH, $file) or warn("Can't open file $file\n");
if ($file =~ /(2009\d{4})/) {
$dt = $+;
}
%hash = {};
while(<FH>) {
#fields = split(/~/);
$subnet = $fields[0];
$client = $fields[2];
$hash{$subnet}{$client}++;
}
close(FH);
$file = "$dt.csv";
open(FH, ">$file") or die("Can't open $file for output");
foreach $subnet (sort keys %hash) {
$tot = keys(%{$hash{$subnet}});
$days{$dt}{$subnet} = $tot;
print FH "$subnet, $tot\n";
push #{$subnet}, $tot;
}
close(FH);
}
foreach $day (sort keys %days) {
foreach $subnet (sort keys %{$days{$day}}) {
$tot = $i = 0;
foreach $amt (#{$subnet}) {
$i++;
$tot += $amt;
}
print "$subnet," . int($tot/$i) . "\n";
}
}
How can I eliminate the need for the sort | uniq process outside of Perl? The last foreach gets me the subnet ids which are the 'anonymous' names for the arrays. It generates these multiple times (one for each day that subnet was used).
but this seemed easier than combining
spreadsheets in excel.
Actually, modules like Spreadsheet::ParseExcel make that really easy, in most cases. You still have to deal with rows as if from CSV or the "A1" type addressing, but you don't have to do the export step. And then you can output with Spreadsheet::WriteExcel!
I've used these modules to read a spreadsheet of a few hundred checks, sort and arrange and mung the contents, and write to a new one for delivery to an accountant.
In this part:
foreach $subnet (sort keys %hash) {
$tot = keys(%{$hash{$subnet}});
$days{$dt}{$subnet} = $tot;
print FH "$subnet,$tot\n";
push #{$subnet}, $tot;
}
$subnet is a string, but you use it in the last statement as an array reference. Since you don't have strictures on, it treats it as a soft reference to a variable with the name the same as the content of $subnet. Which is okay if you really want to, but it's confusing. As for clarifying the last part...
Update I'm guessing this is what you're looking for, where the subnet value is only saved if it hasn't appeared before, even from another day (?):
use List::Util qw(sum); # List::Util was first released with perl 5.007003 (5.7.3, I think)
my %buckets;
foreach my $day (sort keys %days) {
foreach my $subnet (sort keys %{$days{$day}}) {
next if exists $buckets{$subnet}; # only gives you this value once, regardless of what day it came in
my $total = sum #{$subnet}; # no need to reuse a variable
$buckets{$subnet} = int($total/#{$subnet}; # array in scalar context is number of elements
}
}
use Data::Dumper qw(Dumper);
print Dumper \%buckets;
Building on Anonymous's suggestions, I built a hash of the subnet names to access the arrays:
..
push #{$subnet}, $tot;
$subnets{$subnet}++;
}
close(FH);
}
use List::Util qw(sum); # List::Util was first released with perl 5.007003
foreach my $subnet (sort keys %subnets) {
my $total = sum #{$subnet}; # no need to reuse a variable
print "$subnet," . int($total/#{$subnet}) . "\n"; # array in scalar context is number of elements
}
I am not sure if this is the best solution, but I don't have the duplicates any more.