perl number of lines in a string - string

Using perl, is there any single command which give me the number of lines inside a string?
my $linenum= .... $str ....
It should work for when the string is empty, single line, and multiple lines.

You can count number of newline chars \n in the string (or \r for Mac newline)
my $linenum = $str =~ tr/\n//;

I've adapted #rplantiko's answer into a full subroutine that works the way I picture it, with handling for undef and "". It also knows about how the last line of text can be missing a "\n" and returns the apparent line count ( which is the count of "\n" +1 )
# should work on windows + unix but not the old mac
sub count_lines_in_string {
$_ = shift;
return 0 if( !defined $_ or $_ eq "");
my $lastchar = substr $_, -1,1;
my $numlines = () = /\n/g;
# was last line a whole line with a "\n"?;
return $numlines + ($lastchar ne "\n");
}
say count_lines_in_string("asdf\nasdf\n") ;
say count_lines_in_string undef;
say count_lines_in_string "a";

Try to use a regular expression

Related

Getting the characters of a string up to the first "."

I'm attempting to use Perl's gethostnamebyaddr function. The annoying thing is that it returns the entire domain name in scalar format. I want to parse out only the hostname and discard the rest.
I'm using split to divide the domain name into an array and then taking only the first value but this doesn't seem to work.
#!/usr/bin/perl
use Socket;
my $name;
my $hostname;
my #tmpStr;
$name = gethostbyaddr(inet_aton("192.168.2.3"), AF_INET);
print "$name\n";
#tmpStr = split ".", $name;
$hostname = $tmpStr[0];
print "Host name is $hostname\n";
When the above code is executed, I get the following:
dc1-ent.ent.ped.local
Host name is
According to this website the return value is not a string but is rather a scalar value and so my attempt at splitting it doesn't work.
I can't figure out how to convert it to a string before I can split it or parse out the hostname by itself.
The dot character has special meaning for regular expressions in Perl, and the 1st argument to split is a regular expression. You need to escape the dot:
use warnings;
use strict;
my $name = 'dc1-ent.ent.ped.local';
print "$name\n";
my #tmpStr = split /\./, $name;
my $hostname = $tmpStr[0];
print "Host name is $hostname\n";
This outputs:
dc1-ent.ent.ped.local
Host name is dc1-ent
I would write it like this
my $name = gethostbyaddr(inet_aton('192.168.2.3'), AF_INET);
my ($host) = $name =~ /([^.]+)/;
say $host;
Your problem is not related to gethostbyaddr() but by what follows.
Proof:
DB<1> $name = 'dc1-ent.ent.ped.local';
DB<2> #tmpStr = split ".", $name;
DB<3> print #tmpStr;
(nothing printed)
Try instead using split that way:
DB<8> $name = 'dc1-ent.ent.ped.local';
DB<9> #tmpStr = split(/\./, $name);
DB<10> print #tmpStr;
dc1-ententpedlocal
DB<11> print join(' ', #tmpStr);
dc1-ent ent ped local
DB<12> x #tmpStr;
0 'dc1-ent'
1 'ent'
2 'ped'
3 'local'
Or if you absolutely want a string and not a regex, protect the dot also as your string is still parsed as a regular expression (which is why being explicit with / / has its merits, it forces you to remember that some character have special meaning there, like the dot):
DB<1> $name = 'dc1-ent.ent.ped.local';
DB<2> #tmpStr = split('.', $name);
DB<3> print #tmpStr;
DB<4> #tmpStr = split('\.', $name);
DB<5> x #tmpStr
0 'dc1-ent'
1 'ent'
2 'ped'
3 'local'

How can I get Perl string to keep its original formatting after editing it?

I am attempting to write a code that will encrypt letters with a basic cyclic shift cipher while leaving any character that is not a letter alone. I am trying to do this through the use of a sub that finds the new value for each of the letters. When I run the code now,it formats the result so there is a single space between every encrypted letter instead of keeping the original formatting. I also cannot get the result to be only in lowercase letters.
sub encrypter {
my $letter = shift #_;
if ($letter =~ m/^[a-zA-Z]/) {
$letter =~ y/N-ZA-Mn-za-m/A-Za-z/;
return $letter;
}
else {
return lc($letter);
}
}
print "Input string to be encrypted: ";
my $input = <STDIN>;
chomp $input;
print "$input # USER INPUT\n";
my #inputArray = split (//, $input);
my $i = 0;
my #encryptedArray;
for ($i = 0; $i <= $#inputArray; $i++) {
$encryptedArray[$i] = encrypter($inputArray[$i]);
}
print "#encryptedArray # OUTPUT\n";
The problem is how you are printing the array.
Change this line:
print "#encryptedArray # OUTPUT\n";
to:
print join("", #encryptedArray) . " # OUTPUT\n";
Here is an example that illustrates the problem.
#!/usr/bin/perl
my #array = ("a","b","c","d");
print "#array # OUTPUT\n";
print join("", #array) . " # OUTPUT\n";
Output:
$ perl test.pl
a b c d # OUTPUT
abcd # OUTPUT
According to the Perl documentation on print:
The current value of $, (if any) is printed between each LIST item.
The current value of $\ (if any) is printed after the entire LIST has
been printed.
So two others ways to do it would be:
#!/usr/bin/perl
my #array = ("a","b","c","d");
$,="";
print #array, " #OUTPUT\n";
or
#!/usr/bin/perl
my #array = ("a","b","c","d");
$"="";
print #array, " #OUTPUT\n";
Here is a related answer and here is documentation explaining $" and $,.
Those spaces in your output from $" (list separator) because you use print "#encryptedArray" to print that array, which equals print join($", #encryptedArray), therefore you could disable them by
local $" = '';
or you could join that #encryptedArray by yourself before you print it, just as suggested by #Matt.
Note that there is no need for such complexity. tr/// - also known as y/// - wil convert the whole string for you. Like this
use strict;
use warnings;
print "Input string to be encrypted: ";
chomp(my $input = <STDIN>);
print "$input # USER INPUT\n";
(my $encrypted = $input) =~ tr/N-ZA-Mn-za-m/A-Za-z/;
print "$encrypted # OUTPUT\n";

Get rid of warning in perl number adder code

I am writing a program that takes numbers from the command line until the user enters a blank line.
Should the user enter something that is neither newline nor numeric, it notifies the user, and continues.
While everything works, I have use warnings turned on, and it doesn't seem to like the second if conditional if the enters something invalid.
Argument "foo" isn't numeric in numeric eq (==) at adder.pl line 25, <STDIN> line 4.
I don't like running the program with this warning. How can I improve my code?
This is my program
#!/usr/bin/perl
use strict;
use warnings;
#declare variable
my $number = 0; #final answer
my $input;
#prompt the user
print "Input some integers, line by line. When you are done, press return to add them up." . "\n";
while (1) {
#get input from user
$input = <STDIN>;
#remove newlines
chomp($input);
#user pnches in newline
if ($input eq '') { #if the answer is new line
#quit the loop
last;
} #end of if statement
#user punches in bad input
elsif ($input == 0 && $input ne '0' && $input ne '') {
#tell the user what happened and how to rectify it
print "Input must be an integer." . "\n";
} # end of elsif statement
else {
chomp($input);
$number += $input;
} # end of else statement
} #end of while
print "Total is: $number\n";
Perl does DWIM very well. It is famous for it.
So, whatever language you have come from - it looks like C - forget about checking for both strings and numbers: a Perl scalar variable is whatever you ask it to be.
That means something like
elsif ($input == 0 && $input ne '0' && $input ne '') {
makes little sense. Anything read from the keyboard is initially a string, but it will be a number if you want. You are asking for $input to evaluate as zero but not to be the literal string 0. That applies to very few strings, for instance 00 or 0e0.
I think this is what you meant to write. Please take a look.
Isn't it clearer without comments?
use strict;
use warnings;
print "Input some integers line by line. When you are done, press return to add them up\n";
my $total = 0;
while (<>) {
chomp;
last unless /\S/;
if (/\D/) {
print "Input must be an integer\n";
next;
}
$total += $_;
}
print "Total is: $total\n";
Since Perl is untyped, and you are using $input as both a number and a string, you get that warning because "foo" isn't a number and "==" is used to compare equality of numbers.
You first need to check to see if $input is a number or not. One suggestion:
if ($input =~ /^\d+$/)
{
$number += $input;
}
else
{
print "Input must be an integer.\n";
}

How does the '\b' character in perl work?

According to my research the '\b' character used in perl print statements should act like a "backspace", that is, moving the cursor one character back, and deleting the current character. For this reason, I had planned to use this operation to print operational status on a single line, updating as it progressed. However, I noticed that while the cursor does indeed move back, the characters underfoot are not deleted, and therefore, longer messages remain after shorter print statements. I have compiled the following sample code to explain my findings:
#!/usr/bin/perl
use strict;
use warnings;
my $m;
#set to nonzero so that the screen will update before \n
local $| = 1;
print "Current number shown: ";
$m = "LONG MESSAGE TEMP";
print $m;
print "\b" x length($m);
foreach(1..22) {
$m = $_;
print $m;
print "\b" x length($m);
#sleep 1; #Uncomment to see updates
}
print "\n";
And this was the output:
Current number shown: 22NG MESSAGE TEMP
If this is indeed the correct operation of '\b', is there another escape that deletes the character as well as moving the cursor back? I would like to avoid using '\r' which starts at the beginning of the current line. Otherwise, how am I using the escapes incorrectly?
"\b" is just a fancy way of writing chr(0x08). Your terminal will likely move the cursor rather than display anything, but that's entirely up to it.
If you can rely on it, then you can achieve what you want by overwriting with spaces.
my $last_length = 0;
sub update {
my ($s) = #_;
print("\b" x $last_length);
print(" " x $last_length);
print("\b" x $last_length);
print($s);
$last_length = length($s);
}
Or with less flicker:
my $last_length = 0;
sub update {
my ($s) = #_;
my $diff = $last_length - length($s);
print("\b" x $last_length);
print($s);
print(" " x $diff);
print("\b" x $diff);
$last_length = length($s);
}
Just output some extra space characters to overwrite what you need to overwrite.
#!/usr/bin/env perl
use strict;
use warnings;
use Time::HiRes qw(sleep);
sub backspace {
print "\b" x $_[0];
print " " x $_[0];
print "\b" x $_[0];
}
local $| = 1;
my $m = "LONG MESSAGE TEMP";
print "Current number shown: ", $m;
sleep 1;
for (1..22) {
backspace( length($m) );
$m = $_;
print $m;
sleep 0.2;
}
print "\n";
Depending on how it is used, \b can have a special meaning within a Perl command:
\b is the backspace character only inside a character class. Outside a character class, \b alone is a word-character/non-word-character boundary.
To substitute "def" for each occurrence of the word "ABC" within a file, use the Perl command:
perl -pi -e 's/\bABC\b/def/g' file
which will leave strings such as "ZABCD" unchanged.

Perl: Transfer substring positions between two strings

I'm writing a Perl programm and I've got the following problem:
I have a large list of start and end positions in a string. This positions correspond to substrings in this string. I now want to transfer this positions to a second string. This second string is identical to the first string, except that it has additional hyphen.
Example for original String: "ABCDEF" and one Substring "BCDE"
What I have:
Positions of substring in this original string: Start = 1, End =
4
The original string with additional hyphen: "-AB---CD--E-F---"
What I want:
Position of the substring in the hyphen-string: Start=2, End=10
I have a large list of this substring positions.
I strongly suspect that you have shown a reduced version of the problem, in which case any solution may not work for the real situation.
However, it seems simplest to build a regex by interspersing -* (i.e. zero or more hyphens) between characters.
This program works that way, building a regex of B-*C-*D-*E and comparing it to both of your sample strings.
use strict;
use warnings;
my #strings = qw/ ABCDEF -AB---CD--E-F--- /;
my ($start, $end) = (1, 4);
my $substr = substr $strings[0], $start, $end-$start + 1;
my $regex = join '-*', split //, $substr;
$regex = qr/$regex/;
for my $string (#strings) {
if ($string =~ $regex) {
printf "Substring found at %d to %d in string %s\n", $-[0], $+[0]-1, $string;
}
}
output
Substring found at 1 to 4 in string ABCDEF
Substring found at 2 to 10 in string -AB---CD--E-F---
Does this work for you? It just searches for the characters specified by start and end in the hyphenated string and returns their indices.
sub hyphen_substrings {
my $original = shift;
my $hyphenated = shift;
my #substrings = #_;
my #return;
for my $substring (#substrings) {
my ($start, $end) = #{$substring}[0, 1];
my $start_h = index $hyphenated, substr $original, $start, 1;
my $end_h = index $hyphenated, substr $original, $end, 1;
push #return, [$start_h, $end_h];
}
return #return;
}
use strict;
use warnings;
my $theStringGivenAsAnInputExample="-AB---CD--E-F---";
my $start=1;
my $end=4;
my $theStringGivenAsAnotherInput="ABCDEF";
my $regexp=join("-*",split("",substr($theStringGivenAsAnotherInput,$start,$end))
);
$theStringGivenAsAnInputExample =~ /$regexp/p;
print ${^PREMATCH},"\n";
print ${^POSTMATCH},"\n";
print ${^MATCH},"\n";
my $startPosition = length(${^PREMATCH});
my $finishPosition = length(${^PREMATCH})+length(${^MATCH})-1;
print "start, $startPosition finish, $finishPosition\n";

Resources