How can I format boolean operators with perltidy? - perl

To my knowledge, perltidy is extremely handy and helpful when it comes to code formatting. However, I haven't found a way to fine-tune perltidy. For example, I need the && and || formatted so that there are two spaces before and after them. Like this:
$some && $x > 7;
Can I do it? If so, how?

It's easy enough to rig up your own tidy script with PPI that you can run after perltidy. Proof-of-concept:
use PPI;
my $doc = PPI::Document->new($ARGV[0]);
for my $op (#{$doc->find('PPI::Token::Operator')}) {
if ($op eq '&&' || $op eq '||') {
$op->{content} = " $op ";
}
}
print $doc;
And if we run this script on itself, we get:
$ perl je8tidy.pl je8tidy.pl
use PPI;
my $doc = PPI::Document->new($ARGV[0]);
for my $op (#{$doc->find('PPI::Token::Operator')}) {
if ($op eq '&&' || $op eq '||') {
$op->{content} = " $op ";
}
}
print $doc;
It did insert the extra spaces around the only || operator on line 4.

perltidy does not have an option to add two spaces before and after operators.
Furthermore, if you manually add 2 spaces, then run perltidy, it will convert the 2 spaces to a single space by default. However, there are options for skipping selected lines of code.

Related

How to search for multiple strings in same line using perl?

I know how to extract a line by searching for a single string in a file inside perl script and below command worked perfectly fine which gave the lines having 255.255.255.255 in it.
my #newList = grep /255.255.255.255/, #File1;
However when I want to search for multiple strings(fields) in a file, grep command is not working
I have below file where if sourceipaddress, destipaddr and port number matches, it should extract the entire line and write into an array
gitFile:
access abc permit tcp sourceipaddress sourcesubnet destipaddr destsubnet eq portnumber
This is the way I have chosen to resolve the issue where I'm splitting based on the fields and searching for those fields in an array using grep but it does not seem to work (Tried 5 different ways which are commented below but none of the commands worked). I just want a way to search multiple strings(which includes ipaddress) in a line. Kindly help as I’m struggling with this as I’m new to perl.
my #columns = split(' ',$line);
my $fld0 = $columns[3];
my $fld3 = $columns[6];
my $fld5 = $columns[9];
#my #gitLines = grep {$_ =~ "$fld0" && $_ =~ "$sIP" && $_ =~ "$dIP" && $_ =~ "$fld5"} #gitFile;
#my #gitLines = #gitFile =~ /$fld0|$sIP|$dIP|$fld5/;
#my #gitLines = grep /$fld0/ && /$sIP/ && /$dIP/ &&/$fld5/,#gitFile;
#grep {$fld0} && {$sIP} && {$dIP} && {$fld5} #gitFile;
#my #gitLines = grep /255.255.255.255/ && /$fld0/, #File1;
I'm trying this in Linux GNU/Linux flavor
Without complete code it is not clear what is going on in your program. I infer by context that $line has a template for a line so you extract patterns from it, and that #gitFile has all lines from a file. Then among those lines you want to identify the ones that have all three patterns.
The first attempt should be written as
my #gitLines = grep { /$fld0/ && /$fld1/ && /$fld2/ } #gitFile;
While you may indeed pick your delimiters, for any other than // there must be the m, so you can have grep { m"$fld0" && .. } (there is no value in explicit $_ as it only adds noise). But I find it only obscuring to use uncommon delimiters in this case.
The second attempt is wrong as you cannot match an array. Also, using the alternation | would match even when only one pattern matches.
Another way is to form a regex to parse the line instead of matching separately on each field
my $re = join '.*?', map { quotemeta } (split ' ', $line)[3,6,9];
my #gitLines = grep { /$re/ } #gitFile;
Regex patterns should be built using qr operator but for the simple .*? pattern a string works.
Here the patterns need be present in a line in the exact order, unlike in the grep above. A clear advantage is that it runs regex over the line once, while in grep the engine starts three times.
Note that it is generally better to process files line-by-line, unless there are specific reasons to read the whole file ahead of time. For example
# $line contains patterns that must all match at indices 3,6,9
my $re = join '.*?', map { quotemeta } (split ' ', $line)[3,6,9];
my #gitLines;
open my $fh, '<', $git_file_name or die "Can't open $git_file_name: $!";
while (<$fh>) {
next if not /$re/;
push #gitLines, $_;
}
More than the efficiency this has the advantage of being more easily maintained.
Basicaly, I believe you are trying to find more than 1 match in a line and you have got each line in an array called #gitFile.
I am trying to do it in a simpler way as per my understanding.
$fld0 = 'pattern1';
$fld1 = 'pattern2';
foreach(#gitFile)
{
if(($_=~ m/$fld0/ && $_ =~ m/$fld1/))
{
push(#gitLines ,$_);
}
}

Check if user input string is empty/undef?

Here is the entirety of my perl script:
#!/usr/bin/perl
use v5.10;
use strict;
#use P4;
print "enter location of master_testplan.conf:";
my $master_testplan_conf = <>;
if (chomp($master_testplan_conf) eq "")
{
$master_testplan_conf = 'suites/MAP/master_testplan.conf';
}
print ":" . $master_testplan_conf . ":";
referencing this answer, I thought this would work. However it's not getting the default value inside the if statement for some reason.
What am I doing wrong?
chomp does not work that way. It directly modifies the variable passed to it and returns the number of characters chomped off. Do this instead:
chomp $master_testplan_conf;
if ($master_testplan_conf eq "") {
# etc.
}
chomp modifies its argument and does not return it, so you have to rewrite your condition into something like:
chomp($master_testplan_conf);
if ($master_testplan_conf eq "") {
From the documentation on chomp:
..It returns the total number of characters removed from all its arguments..
So you need to chomp first and then compare to the empty string. For example:
chomp($master_testplan_conf = <>);
if ($master_testplan_conf eq "") {
// set default value
}
A few things:
Chomp changes the string, and returns the number of character chomped. After that input line, chomp $master_testplan_conf is most likely to 1, so you're comparing 1 to the null string.
You can do it this way:
chomp ( $master_testplan_conf = <> );
if you want to do everything on a single line.
That will read your input and do the chomp in one step. Also, the <> operator will take files from the command line and <> will be the first line of the first file on the command line. If you don't want to do that, use <STDIN>:
chomp ( $master_testplan_conf = <STDIN> );
You may want to sanitize your user's input. I would at least remove any leading and ending blanks:
$master_testplan_conf =~ s/^\s*(.*?)\s*$/$1/; # Oh, I wish there was a "trim" command!
This way, if the user accidentally presses spacebar a few times, you don't pick up the spaces. You also may want to test for the file's existence too:
if ( not -f $master_testplan_conf ) {
die qq(File "$master_testplan_conf" not found);
}
I also recommend to use:
if ( not defined $master_testplan_conf or $master_testplan_conf eq "" ) {
for your if statement. This will test whether $master_test_conf is actually defined and not merely a null string. Right now, this doesn't matter since the user has to at least enter a \n. The $master_testplan_conf stroll will never be null.
However, it may matter if you decide to use Getopt::Long.
You're interested in the file and not the string, per se, so use Perl file tests, instead. In this case, use the file test for existence (-e):
if (-e $master_testplan_conf) {
This gets to the heart of the matter and lets you know whether the input exists in the file system, or not.
A regex can be handy to check without altering anything:
if ($master_testplan_conf =~ /^\s*$/)
{
$master_testplan_conf = 'suites/MAP/master_testplan.conf';
}
to check undef also:
if (!defined $master_testplan_conf || $master_testplan_conf =~ /^\s*$/)
{
$master_testplan_conf = 'suites/MAP/master_testplan.conf';
}

perl exact string match

I have following Perl code to prompt user for yes/no answer. If the user enters anything else than yes/no, keep prompting. No other word is acceptable. I don't know why this code doesn't work. I tested with answer "noooooo" and I was expecting it to prompt again but it does not enter the while loop.
Can anyone help find my mistake here?
#files = A, B, C;
foreach $c (#files) {
if (-e $c ) {
print " $c already exists. Do you want to overwrite? (yes or no): ";
chomp ($file_yes_no = <STDIN>);
while ($file_yes_no !~ m/yes{1}|no{1}/i ) {
print "$c already exists. Do you want to overwrite? (yes or no): ";
chomp ($file_yes_no = <STDIN>);
}
if ($file_yes_no =~ m/yes/i ) {
if (system ("cp -f /home/old_path/ /home/new_path/ == 0) {
print "$c successfully copied;
} else {
die "Error: Copy failed, Check the message above";
}
}
else { print "No files copied\n; }
I would just use the string equality operator eq instead of a regex.
if( $file_yes_no eq 'yes' ) ...
If I wanted it case insensitive I'd first convert to lowercase with lc.
The problem with your regex is it will happily match any string containing the letters yes sequentially. If you wish, you can match the start and end of the string like this:
if ($file_yes_no =~ m/^yes$/i ) ...
But I personally prefer the first option.
Oh, I missed the first part... Hmmmm. Same deal, if you must use regex.
m/^(yes|no)$/i
Once again I'd be more inclined to avoid regex
You should use following Perl regular expression for matching only yes or no (case insensitive):
m/^(yes|no)$/i
For yes only, use:
m/^yes$/i
Because you're using a regular expression. You could write the regular expression to match the beginning or end of the string ... like this:
while( $file_yes_no !~ /^(yes|no)$/ ) {
The ^ and $ are the beginning and end of the string. Also you can omit the m.
Or you could just check the values explicitly:
while( $file_yes_no ne "yes" and $file_yes_no ne "no" ) {
Also you have a typo in your system command but I'm assuming that was just copying it here. You really shouldn't branch out to a shell for that. Look into File::Copy which gives you a copy function

Perl split() Function Not Handling Pipe Character Saved As A Variable

I'm running into a little trouble with Perl's built-in split function. I'm creating a script that edits the first line of a CSV file which uses a pipe for column delimitation. Below is the first line:
KEY|H1|H2|H3
However, when I run the script, here is the output I receive:
Col1|Col2|Col3|Col4|Col5|Col6|Col7|Col8|Col9|Col10|Col11|Col12|Col13|
I have a feeling that Perl doesn't like the fact that I use a variable to actually do the split, and in this case, the variable is a pipe. When I replace the variable with an actual pipe, it works perfectly as intended. How could I go about splitting the line properly when using pipe delimitation, even when passing in a variable? Also, as a silly caveat, I don't have permissions to install an external module from CPAN, so I have to stick with built-in functions and modules.
For context, here is the necessary part of my script:
our $opt_h;
our $opt_f;
our $opt_d;
# Get user input - filename and delimiter
getopts("f:d:h");
if (defined($opt_h)) {
&print_help;
exit 0;
}
if (!defined($opt_f)) {
$opt_f = &promptUser("Enter the Source file, for example /qa/data/testdata/prod.csv");
}
if (!defined($opt_d)) {
$opt_d = "\|";
}
my $delimiter = "\|";
my $temp_file = $opt_f;
my #temp_file = split(/\./, $temp_file);
$temp_file = $temp_file[0]."_add-headers.".$temp_file[1];
open(source_file, "<", $opt_f) or die "Err opening $opt_f: $!";
open(temp_file, ">", $temp_file) or die "Error opening $temp_file: $!";
my $source_header = <source_file>;
my #source_header_columns = split(/${delimiter}/, $source_header);
chomp(#source_header_columns);
for (my $i=1; $i<=scalar(#source_header_columns); $i++) {
print temp_file "Col$i";
print temp_file "$delimiter";
}
print temp_file "\n";
while (my $line = <source_file>) {
print temp_file "$line";
}
close(source_file);
close(temp_file);
The first argument to split is a compiled regular expression or a regular expression pattern. If you want to split on text |. You'll need to pass a pattern that matches |.
quotemeta creates a pattern from a string that matches that string.
my $delimiter = '|';
my $delimiter_pat = quotemeta($delimiter);
split $delimiter_pat
Alternatively, quotemeta can be accessed as \Q..\E inside double-quoted strings and the like.
my $delimiter = '|';
split /\Q$delimiter\E/
The \E can even be omitted if it's at the end.
my $delimiter = '|';
split /\Q$delimiter/
I mentioned that split also accepts a compiled regular expression.
my $delimiter = '|';
my $delimiter_re = qr/\Q$delimiter/;
split $delimiter_re
If you don't mind hardcoding the regular expression, that's the same as
my $delimiter_re = qr/\|/;
split $delimiter_re
First, the | isn't special inside doublequotes. Setting $delimiter to just "|" and then making sure it is quoted later would work or possibly setting $delimiter to "\\|" would be ok by itself.
Second, the | is special inside regex so you want to quote it there. The safest way to do that is ask perl to quote your code for you. Use the \Q...\E construct within the regex to mark out data you want quoted.
my #source_header_columns = split(/\Q${delimiter}\E/, $source_header);
see: http://perldoc.perl.org/perlre.html
It seems as all you want to do is count the fields in the header, and print the header. Might I suggest something a bit simpler than using split?
my $str="KEY|H1|H2|H3";
my $count=0;
$str =~ s/\w+/"Col" . ++$count/eg;
print "$str\n";
Works with most any delimeter (except alphanumeric and underscore), it also saves the number of fields in $count, in case you need it later.
Here's another version. This one uses the character class brackets instead, to specify "any character but this", which is just another way of defining a delimeter. You can specify delimeter from the command-line. You can use your getopts as well, but I just used a simple shift.
my $d = shift || '[^|]';
if ( $d !~ /^\[/ ) {
$d = '[^' . $d . ']';
}
my $str="KEY|H1|H2|H3";
my $count=0;
$str =~ s/$d+/"Col" . ++$count/eg;
print "$str\n";
By using the brackets, you do not need to worry about escaping metacharacters.
#!/usr/bin/perl
use Data::Dumper;
use strict;
my $delimeter="\\|";
my $string="A|B|C|DD|E";
my #arr=split(/$delimeter/,$string);
print Dumper(#arr)."\n";
output:
$VAR1 = 'A';
$VAR2 = 'B';
$VAR3 = 'C';
$VAR4 = 'DD';
$VAR5 = 'E';
seems you need define delimeter as \\|

How to write a simple sub in perl that takes a string and returns a string?

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.