There is probably an easy solution to this, but I can't figure it out. I am looking to:
take a CSV file into an array
loop through the array and split fields into variables
if the array field is empty then set the variable to "N/A"
Note: It is only setting the $variable to "N/A" that I cannot get working.
For example:
foreach $var (#list) {
($name,$date,$size, etc...)=split(/,\"/,$var);
}
How would I set $date to "N/A" if the field in the array is empty?
so to produce:
$name = Jim
$date = N/A
$size = small
I hope this makes sense and is easy to fix.
-Thanks
Assuming the variable $date is undefined when "empty":
if (!defined($date)) {
$date = 'N/A';
}
Or more concisely:
$date //= 'N/A';
Or if it really is an empty string, i.e. $date = ''; (this will also work in the case where $date is undefined, but you don't want to use this if you only want to identify the case where it is undefined):
if ($date eq '') {
$date = 'N/A';
}
Or more concisely (note that this will also set $date to N/A if $date is '0' due to Perl's weak typing):
$date ||= 'N/A';
As far as your third bullet point and the actual question: to check for emptiness:
For empty string, you can either do the above-mentioned eq "", or you can check the string length: $var = "N/A" unless length($var);;
For an undefined of empty string, in Perl 5.10 you can use the "defined-or" (//) operator to do the short version: $var = "N/A" unless length($var // '');
In Perl before 5.10 where "defined-or" is not available, you will either have to spell out the defined check: $var = "N/A" unless defined $var && length($var);
... or, you can just stop caring about undefined warnings by turning them off (h/t brian d foy):
no warnings 'uninitialized';
$_ = "N/A" unless length($_) foreach ($name,$date,$size, etc...);
use warnings 'uninitialized'; # Always turn back on.
However, please note that you also should consider a different approach to the first two bullet points. Implementing your own CSV parser which is 100% correct is not trivial - for example, your sample code will break if any of the fields contain a double quote.
Instead, you should always use one of the standard Perl CSV parsers, such as Text::CSV_XS.
$name = "N/A" if (!defined($name) || ($name eq ""))
$date = "N/A" if (!defined($date) || ($date eq ""))
$size = "N/A" if (!defined($size) || ($size eq ""))
Make sure you are using string comparison for comparing strings :)
What will be the input like if date is missing? If the input is: somename,200 (where 200 is size), then date would be set as 200 right?
If the input is like this
somename,,200
where 200 is size, and because date is unavailable it is set to empty. Then you can do a simple if-check:
if($date eq '')
{
$date = "NA";
}
Note $date will be defined, it will be just set to empty
if ($date eq '') { print "STRING IS EMPTY\n" } else { Print "STRING IS NOT EMPTY\n";}
we can use the above code to identify the empty string ,and using the regular expression is more efficient. The "=~" operator and using regular expression also we can also this problem.
Related
This is sort of what I am wanting to do. At present mm returns nothing, while searchname returns the expected value.
This is a perl script embedded in a web page.
I have tried numerous approaches to this code but nothing seems to provide the results I desire. I think it is just a case of syntax.
# search for an item
if ($modtype eq "search") {
$searchname=$modname;
print "Value of searchname $searchname\n";
my #mm = grep{$searchname} #names;
print "Value of mm #mm\n";
if ($mm eq $searchname) {
print "$searchname found!\n";
}
else {
print "$searchname not Found\n";
}
}
my #mm = grep { $_ eq $searchname } #names;
if (#mm) {
print "found\n";
}
grep takes a boolean expression, not just a variable. In that expression, $_ refers to the current list element. By using an equality comparison we get (in #mm) all elements of #names that are equal to $searchname, if any.
To check whether an array is empty, you can simply use it in boolean context, as in if (#mm).
If you don't care about the found elements themselves, just whether there are any, you can use grep in scalar context:
my $count = grep { $_ eq $searchname } #names;
if ($count > 0) {
print "found $count results\n";
}
This will give you the number of matching elements.
If you don't need to know that number, just whether there was any result at all, you can use any from List::Util:
use List::Util qw(any);
if (any { $_ eq $searchname } #names) {
...
}
If #names is big, this is potentially more efficient because it can stop after the first match is found.
I'm not sure what $mm refers to in your code. Did you start your code with use strict; use warnings;? If not, you should.
Looks like you misunderstand a couple of things.
my #mm = grep{$searchname} #names;
The grep() function takes two arguments. A block of code ({ $searchname }) and a list of values (#names). For each value in the list, it puts the value into $_ and executes the code block. If the code block returns a true value then the contents of $_ is added to the output list.
Your block of code ignores $_ and just checks for the value of $searchname. That is very likely to always be true, so all of the values from #names get copied into #mm.
I think it's more likely that you want:
my #mm = grep{ $_ eq $searchname } #names;
Secondly, you suddenly start using a new variable called $mm. I suspect you're getting confused between #mm and $mm which are completely different variables with no connection with each other.
I think what you're actually trying to do is to look at the first element of #mm so you want:
if ($mm[0] eq $searchname)
But, given that values only end up in #mm if they are equal to $searchname (because that's what your grep() does), I think you really just want to check whether or not anything ended up in #mm. So you should use:
if (#mm)
Which is, in my opinion, easier to understand.
I have the following code:
foreach $string (#strings) {
if ($hash{$string}) {
$letter = $hash{$string};
$text .= $letter;
}
}
where $letter is one character or one number (for example: a, e, o, 2, 5...). It creates a fine text, but I have problems with appending the number 0.
If $letter = 0 it is not concatenated, giving for example 28 instead of 2008. However, if $letter at that moment is 1 or aaaa, it gives 2118 or 2aaaaaaaa8.
I was thinking that it might be something related to 0 and FALSE in Perl... might be?
Many thanks.
You test to see if the entry exists using if ($hash{$string}) but that also checks that the value is true. Use if (defined $hash{$string}) instead.
I was thinking that it might be something related to 0 and FALSE in Perl... might be?
Yes, that is the issue.
The correct way to check whether a hash key is defined as follows
foreach $string (#strings)
{
if (defined $hash{$string}) # Check if key is defined
{
$letter = $hash{$string};
$text .= $letter;
}
}
A value of 0 is defined but false. Check definedness rather than truth:
if (defined $hash{$string}) {
or using the defined-or operator:
$text .= $hash{$string} // '';
I want to write $1 on other line for replacement;
my $converting_rules = +{
'(.+?)' => '$1',
};
my $pre = $converting_rule_key;
my $post = $converting_rules->{$converting_rule_key};
#$path_file =~ s/$pre/$post/; // Bad...
$path_file =~ s/$pre/$1/; // Good!
On Bad, $1 is recognized as a string '$1'.
But I wqnt to treat it matched string.
I have no idea what to do...plz help me!
The trouble is that s/$pre/$post/ interpolates the variables $pre and $post, but will not recursively interpolate anything in them that happens to look like a variable. So you want to add an extra eval to the replacement, with the /ee flag:
$path_file =~ s/$pre/$post/ee;
$x = '$1.00';
print qq/$x/;
prints $1.00, so it's no surprise that
$x = '$1.00';
s/(abc)/$x/;
substitutes with $1.00.
What you have there is a template, yet you did nothing to process this template. String::Interpolate can handle such templates.
use String::Interpolate qw( interpolate );
$rep = '$1';
s/$pat/ interpolate($rep) /e;
I am having quite a bit of trouble with a Perl script I am writing. I want to compare an element of an array to a variable I have to see if they are true. For some reason I cannot seem to get the comparison operation to work correctly. It will either evaluate at true all the time (even when outputting both strings clearly shows they are not the same), or it will always be false and never evaluate (even if they are the same). I have found an example of just this kind of comparison operation on another website, but when I use it it doesn't work. Am I missing something? Is the variable type I take from the file not a string? (Can't be an integer as far as I can tell as it is an IP address).
$ipaddress = '192.43.2.130'
if ($address[0] == ' ')
{
open (FH, "serverips.txt") or die "Crossroads could not find a list of backend servers";
#address = <FH>;
close(FH);
print $address[0];
print $address[1];
}
for ($i = 0; $i < #address; $i++)
{
print "hello";
if ($address[$i] eq $ipaddress)
{print $address[$i];
$file = "server_$i";
print "I got here first";
goto SENDING;}
}
SENDING:
print " I am here";
I am pretty weak in Perl, so forgive me for any rookie mistakes/assumptions I may have made in my very meager bit of code. Thank you for you time.
if ($address[0] == ' ')
{
open (FH, "serverips.txt") or die "Crossroads could not find a list of backend servers";
#address = <FH>;
close(FH);
You have several issues with this code here. First you should use strict because it would tell you that #address is being used before it's defined and you're also using numeric comparison on a string.
Secondly you aren't creating an array of the address in the file. You need to loop through the lines of the file to add each address:
my #address = ();
while( my $addr = <FH> ) {
chomp($addr); # removes the newline character
push(#address, $addr);
}
However you really don't need to push into an array at all. Just loop through the file and find the IP. Also don't use goto. That's what last is for.
while( my $addr = <FH> ) {
chomp($addr);
if( $addr eq $ipaddress ) {
$file = "server_$i";
print $addr,"\n";
print "I got here first"; # not sure what this means
last; # breaks out of the loop
}
}
When you're reading in from a file like that, you should use chomp() when doing a comparison with that line. When you do:
print $address[0];
print $address[1];
The output is on two separate lines, even though you haven't explicitly printed a newline. That's because $address[$i] contains a newline at the end. chomp removes this.
if ($address[$i] eq $ipaddress)
could read
my $currentIP = $address[$i];
chomp($currentIP);
if ($currentIP eq $ipaddress)
Once you're familiar with chomp, you could even use:
chomp(my $currentIP = $address[$i]);
if ($currentIP eq $ipaddress)
Also, please replace the goto with a last statement. That's perl's equivalent of C's break.
Also, from your comment on Jack's answer:
Here's some code you can use for finding how long it's been since a file was modified:
my $secondsSinceUpdate = time() - stat('filename.txt')->mtime;
You probably are having an issue with newlines. Try using chomp($address[$i]).
First of all, please don't use goto. Every time you use goto, the baby Jesus cries while killing a kitten.
Secondly, your code is a bit confusing in that you seem to be populating #address after starting the if($address[0] == '') statement (not to mention that that if should be if($address[0] eq '')).
If you're trying to compare each element of #address with $ipaddress for equality, you can do something like the following
Note: This code assumes that you've populated #address.
my $num_matches=0;
foreach(#address)
{
$num_matches++ if $_ eq $ipaddress;
}
if($num_matches)
{
#You've got a match! Do something.
}
else
{
#You don't have any matches. This may or may not be bad. Do something else.
}
Alternatively, you can use the grep operator to get any and all matches from #address:
my #matches=grep{$_ eq $ipaddress}#address;
if(#matches)
{
#You've got matches.
}
else
{
#Sorry, no matches.
}
Finally, if you're using a version of Perl that is 5.10 or higher, you can use the smart match operator (ie ~~):
if($ipaddress~~#address)
{
#You've got a match!
}
else
{
#Nope, no matches.
}
When you read from a file like that you include the end-of-line character (generally \n) in each element. Use chomp #address; to get rid of it.
Also, use last; to exit the loop; goto is practically never needed.
Here's a rather idiomatic rewrite of your code. I'm excluding some of your logic that you might need, but isn't clear why:
$ipaddress = '192.43.2.130'
open (FH, "serverips.txt") or die "Crossroads could not find a list of backend servers";
while (<FH>) { # loop over the file, using the default input space
chomp; # remove end-of-line
last if ($_ eq $ipaddress); # a RE could easily be used here also, but keep the exact match
}
close(FH);
$file = "server_$."; # $. is the line number - it's not necessary to keep track yourself
print "The file is $file\n";
Some people dislike using perl's implicit variables (like $_ and $.) but they're not that hard to keep track of. perldoc perlvar lists all these variables and explains their usage.
Regarding the exact match vs. "RE" (regular expression, or regexp - see perldoc perlre for lots of gory details) -- the syntax for testing a RE against the default input space ($_) is very simple. Instead of
last if ($_ eq $ipaddress);
you could use
last if (/$ipaddress/);
Although treating an ip address as a regular expression (where . has a special meaning) is probably not a good idea.
sub getHeading
{
my $var = $_[0];
my $match;
if ($match = ($var =~ m/$fivetonine/))
{
return "=";
}
if ($match = ($var =~ m/$tentofourteen/))
{
return "==";
}
if ($match = ($var =~ m/$fifteentonineteen/)){
return "===";
}
return "===";
}
my $ref_to_getHeading = \getHeading;
and I am calling it via:
$html =~ s/(.*)<font size="([^"]+)">(.+)<\/font>(.*)/$ref_to_getHeading($2)$1$3$4$ref_to_getHeading($2)/m;
I am wanting to pass a string in to this function, I want to check if it is one of 3 different matches and return the appropriate number of = signs, I am doing this wrong but I can't figure out how to make it take parameters? I get a run time error saying $var is initialised? I tried using #_ but I don't really understand what the difference is.
Any help much appreciated, I have never written perl before and this is my first real program.
Double mistake there.
First, you aren't taking a reference to a function - You need to add the ampersand.
But even if you do that, it won't work. You are missing the /e flag in your substitution: You can't dereference a coderef within a string like you'd normally do with (scalar|hash|array)ref:
my $example = sub { return "hello" };
say "$example->()"; #Will stringify the coderef.
You either need the /e flag,
$html =~ s/etc/$ref_to_getHeading->($2) . "$1$3$4" . $ref_to_getHeading->($2)/em;
Or a little trick:
$html =~ s/etc/#{[$ref_to_getHeading->($2)]}$1$3$4#{[$ref_to_getHeading->($2)]}/m;
EDIT: Gosh, am I a slow typist..
Anyhow, with either way, you should be able to call the sub directly, so no need for the coderef.
The line my $ref_to_getHeading = \getHeading; doesn't do what you think it does. To take a reference to a subroutine:
my $ref_to_getHeading = \&getHeading; # note the &
So you were actually calling getHeading and storing the result. Since you passed no arguments, you got the undefined value warning.
The substitution however will never call the coderef, for that to happen, you need to add the e modifier to run the replacement text through eval:
$html =~ s/.../join '' => getHeading($2), $1, $3, $4, getHeading($2)/me;
you may run into issues here with getHeading resetting the match vars too early. In which case, try writing it this way:
$html =~ s{...}{
my $body = $1 . $3 . $4;
my $heading = getHeading($2);
$heading . $body . $heading
}me;
The bracket change for s/// was not necessary, I just find it easier to read a multi-line curly block.