Perl : string of variable within a variable - string

Here is an example of what i'm trying to do:
I want to "defined" a name for the input and then when it's taken into a function, only then it will substitute all the 3 variables.
$place_holder = 'f${file_case}_lalal_${subcase}_${test}';
.... somewhere else in another function:
read file containing 3 set of numbers on each line that represents the $file_case, $subcase, $test
while(<IN>){
($file_case, $subcase, $tset) = split;
$input = $place_holder #### line #3 here needs to fix
print " $input \n";
}
Unfortunately, it prints out f${file_case}lalal${subcase}_${test} for every single line. I want those variables to be substituted. How do I do that, how
do I change line #3 to be able to output as i wanted ? I don't want to defined the input name in the subroutine, it has to be in the main.

You can do it using subroutines for example, if that satisfies your criteria
use warnings;
use strict;
my $place_holder = sub {
my ($file_case, $subcase, $test) = #_;
return "f${file_case}_lalal_${subcase}_${test}";
}
# ...
while (<IN>) {
my ($file_case, $subcase, $tset) = split;
#
# Code to validate input
#
my $input = $place_holder->($file_case, $subcase, $tset);
print "$input\n";
}
I've used code reference with an anonymous subroutine in anticipation of uses that may benefit from it, but for the specified task alone you can use a normal subroutine as well.
Note that you have $test and $tset, which doesn't affect the above but may be typos.

You may use the String::Interpolate module, like this
use String::Interpolate 'interpolate';
my $place_holder = 'f${file_case}_lalal_${subcase}_${test}';
while ( <IN> ) {
my ($file_case, $subcase, $test) = split;
my $input = interpolate($place_holder);
print "$input\n";
}
The module gives access to Perl's built-in C code that performs double-quote interpolation, so it is generally fast and accurate

A while after I posted, I found a way to do it.
in the ## line 3, do this:
($input = $place_holder) =~ s/(\${w+})/$1/eeg;
and everything works. Yes the above tset is a typo, meant to be test. Thank for everybody's response.

Try eval while(<IN>){ ($file_case, $subcase, $tset) = split; $input = eval $place_holder #### line #3 here needs to fix print " $input \n"; }

Related

Need to open a file and replace multiple strings

I have a really big xml file. It has certain incrementing numbers inside, which i would like to replace with a different incrementing number. I've looked and here is what someone suggested here before. Unfortunately i cant get it to work :(
In the code below all instances of 40960 should be replaced with 41984, all instances of 40961 with 41985 etc. Nothing happens. What am i doing wrong?
use strict;
use warnings;
my $old = 40960;
my $new = 41984;
my $string;
my $file = 'file.txt';
rename($file, $file.'.bak');
open(IN, '<'.$file.'.bak') or die $!;
open(OUT, '>'.$file) or die $!;
$old++;
$new++;
for (my $i = 0; $i < 42; $i++) {
while(<IN>) {
$_ =~ s/$old/$new/g;
print OUT $_;
}
}
close(IN);
close(OUT);
Other answers give you better solutions to your problem. Mine concentrates on explaining why your code didn't work.
The core of your code is here:
$old++;
$new++;
for (my $i = 0; $i < 42; $i++) {
while(<IN>) {
$_ =~ s/$old/$new/g;
print OUT $_;
}
}
You increment the values of $old and $new outside of your loops. And you never change those values again. So you're only making the same substitution (changing 40961 to 41985) 42 times. You never try to change any other numbers.
Also, look at the while loop that reads from IN. On your first iteration (when $i is 0) you read all of the data from IN and the file pointer is left at the end of the file. So when you go into the while loop again on your second iteration (and all subsequent iterations) you read no data at all from the file. You need to reset the file pointer to the start of your file at the end of each iteration.
Oh, and the basic logic is wrong. If you think about it, you'll end up writing each line to the output file 42 times. You need to do all possible substitutions before writing the line. So your inner loop needs to be the outer loop (and vice versa).
Putting those suggestions together, you need something like this:
my $old = 40960;
my $change = 1024;
while (<IN>) {
# Easier way to write your loop
for my $i ( 1 .. 42 ) {
my $new = $old + $change;
# Use \b to mark word boundaries
s/\b$old\b/$new/g;
$old++;
}
# Print each output line only once
print OUT $_;
}
Here's an example that works line by line, so the size of file is immaterial. The example assumes you want to replace things like "45678", but not "fred45678". The example also assumes that there is a range of numbers, and you want them replaced with a new range offset by a constant.
#!/usr/bin/perl
use strict;
use warnings;
use constant MIN => 40000;
use constant MAX => 90000;
use constant DIFF => +1024;
sub repl { $_[0] >= MIN && $_[0] <= MAX ? $_[0] + DIFF : $_[0] }
while (<>) {
s/\b(\d+)\b/repl($1)/eg;
print;
}
exit(0);
Invoked with the file you want to transform as an argument, it produces altered output on stdout. With the following input ...
foo bar 123
40000 50000 60000 99999
fred60000
fred 60000 fred
... it produces this output.
foo bar 123
41024 51024 61024 99999
fred60000
fred 61024 fred
There are a couple of classic Perlisms here, but the example shouldn't be hard to follow if you RTFM appropriately.
Here is an alternative way which reads the input file into a string and does all the substitutions at once:
use strict;
use warnings;
{
my $old = 40960;
my $new = 41984;
my ($regexp) = map { qr/$_/ } join '|', map { $old + $_ } 0..41;
my $file = 'file.txt';
rename($file, $file.'.bak');
open(IN, '<'.$file.'.bak') or die $!;
my $str = do {local $/; <IN>};
close IN;
$str =~ s/($regexp)/do_subst($1, $old, $new)/ge;
open(OUT, '>'.$file) or die $!;
print OUT $str;
close OUT;
}
sub do_subst {
my ( $old, $old_base, $new_base ) = #_;
my $i = $old - $old_base;
my $new = $new_base + $i;
return $new;
}
Note: Can probably be made more efficient by using Regexp::Assemble

Perl - Searching values in a log file and store/print them as a string.

I would like to search values after a specific word (Current Value = ) in a log file, and makes a string with values.
vcs_output.log: a log file
** Fault injection **
Count = 1533
0: Path = cmp_top.iop.sparc0.exu.alu.byp_alu_rcc_data_e[6]
0: Current value = x
1: Path = cmp_top.iop.sparc0.exu.alu.byp_alu_rs3_data_e[51]
1: Current value = x
2: Path = cmp_top.iop.sparc0.exu.alu.byp_alu_rs1_data_e[3]
2: Current value = 1
3: Path = cmp_top.iop.sparc0.exu.alu.shft_alu_shift_out_e[18]
3: Current value = 0
4: Path = cmp_top.iop.sparc0.exu.alu.byp_alu_rs3_data_e[17]
4: Current value = x
5: Path = cmp_top.iop.sparc0.exu.alu.byp_alu_rs1_data_e[43]
5: Current value = 0
6: Path = cmp_top.iop.sparc0.exu.alu.byp_alu_rcc_data_e[38]
6: Current value = x
7: Path = cmp_top.iop.sparc0.exu.alu.byp_alu_rs2_data_e_l[30]
7: Current value = 1
.
.
.
If I store values after "Current value = ", then x,x,1,0,x,0,x,1. I ultimately save/print them as a string such as xx10x0x1.
Here is my code
code.pl:
#!/usr/bin/perl
use strict;
use warnings;
##### Read input
open ( my $input_fh, '<', 'vcs_output.log' ) or die $!;
chomp ( my #input = <$input_fh> );
my $i=0;
my #arr;
while (#input) {
if (/Current value = /)
$arr[i]= $input; # put the matched value to array
}
}
## make a string from the array using an additional loop
close ( $input_fh );
I think there is a way to make a string in one loop (or even not using a loop). Please advise me to make it. Any suggestion is appreciated.
You can do both that you ask for.
To build a string directly, just append to it what you capture in the regex
my $string;
while (<$input_fh>)
{
my ($val) = /Current\s*value\s*=\s*(.*)/;
$string .= $val;
}
If the match fails then $val is an empty string, so we don't have to test. You can also write the whole while loop in one line
$string .= (/Current\s*value\s*=\s*(.*)/)[0] while <$input_fh>;
but I don't see why that would be necessary. Note that this reads from the filehandle, and line by line. There is no reason to first read all lines into an array.
To avoid (explicit) looping, you can read all lines and pass them through map, naively as
my $string = join '',
map { (/Current\s*value\s*=\s*(.*)/) ? $1 : () } <$input_fh>;
Since map needs a list, the filehandle is in list context, returning the list of all lines in the file. Then each is processed by code in map's block, and its output list is then joined.
The trick map { ($test) ? $val : () } uses map to also do grep's job, to filter -- the empty list that is returned if $test fails is flattened into the output list, thus disappearing. The "test" here is the regex match, which in the scalar context returns true/false, while the capture sets $1.
But, like above, we can return the first element of the list that match returns, instead of testing whether the match was successful. And since we are in map we can in fact return the "whole" list
my $string = join '',
map { /Current\s*value\s*=\s*(.*)/ } <$input_fh>;
what may be clearer here.
Comments on the code in the question
the while (#input) is an infinite loop, since #input never gets depleted. You'd need foreach (#input) -- but better just read the filehandle, while (<$input_fh>)
your regex does match on a line with that string, but it doesn't attempt to match the pattern that you need (what follows =). Once you add that, it need be captured as well, by ()
you can assign to the i-th element (which should be $i) but then you'd have to increment $i as you go. Most of the time it is better to just push #array, $value
You can use capturing parentheses to grab the string you want:
use strict;
use warnings;
my #arr;
open ( my $input_fh, '<', 'vcs_output.log' ) or die $!;
while (<$input_fh>) {
if (/Current value = (.)/) {
push #arr, $1;
}
}
close ( $input_fh );
print "#arr\n";
__END__
x x 1 0 x 0 x 1
Use grep and perlre
http://perldoc.perl.org/functions/grep.html
http://perldoc.perl.org/perlre.html
If on a non-Unix environment then...
#!/usr/bin/perl -w
use strict;
open (my $fh, '<', "vcs_output.log");
chomp (my #lines = <$fh>);
# Filter for lines which contain string 'Current value'
#lines = grep{/Current value/} #lines;
# Substitute out what we don't want... leaving us with the 'xx10x0x1'
#lines = map { $_ =~ s/.*Current value = //;$_} #lines;
my $str = join('', #lines);
print $str;
Otherwise...
#!/usr/bin/perl -w
use strict;
my $output = `grep "Current value" vcs_output.log | sed 's/.*Current value = //'`;
$output =~ s/\n//g;
print $output;

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";
}

Replacing values of a file based on a conf file

I have a conf file which has the format of variable="value" where values may have special characters as well. An example line is:
LINE_D="(L#'id' == 'log') AND L#'id' IS NULL"
I have another file F which should replace values based on this conf file. For example, if there is line in F
PRINT '$LINE_D'
it should be replaced by
PRINT '(L#'id' == 'log') AND L#'id' IS NULL'
How can I a program in shell script which takes conf and F and generate the values in F replaced.
Thanks
Your definition of what's required leaves lots of gaps, so you'll probably need to tweak this script. It is a cut-down version of a more complex script originally designed to process makefiles. That means there is probably material you could remove from here without causing trouble, though I've gotten rid of most of the extraneous processing.
#!usr/bin/env perl
#
# Note: this script can take input from stdin or from one or more files.
# For example, either of the following will work:
# cat config file | setmacro
# setmacro file
use strict;
use warnings;
use Getopt::Std;
# Usage:
# -b -- omit blank lines
# -c -- omit comments
# -d -- debug mode (verbose)
# -e -- omit the environment
my %opt;
my %MACROS;
my $input_line;
die "Usage: $0 [-bcde] [file ...]" unless getopts('bcde', \%opt);
# Copy environment into hash for MAKE macros
%MACROS = %ENV unless $opt{e};
my $rx_macro = qr/\${?([A-Za-z]\w*)}?/; # Matches $PQR} but ideally shouldn't
# For each line in each file specified on the command line (or stdin by default)
while ($input_line = <>)
{
chomp $input_line;
do_line($input_line);
}
# Expand macros in given value
sub macro_expand
{
my($value) = #_;
print "-->> macro_expand: $value\n" if $opt{d};
while ($value =~ $rx_macro)
{
print "Found macro = $1\n" if $opt{d};
my($env) = $MACROS{$1};
$env = "" unless defined $env;
$value = $` . $env . $';
}
print "<<-- macro_expand: $value\n" if $opt{d};
return($value);
}
# routine to recognize macros
sub do_line
{
my($line) = #_;
if ($line =~ /^\s*$/o)
{
# Blank line
print "$line\n" unless $opt{b};
}
elsif ($line =~ /^\s*#/o)
{
# Comment line
print "$line\n" unless $opt{c};
}
elsif ($line =~ /^\s*([A-Za-z]\w*)\s*=\s*(.*)\s*$/o)
{
# Macro definition
print "Macro: $line\n" if $opt{d};
my $lhs = $1;
my $rhs = $2;
$rhs = $1 if $rhs =~ m/^"(.*)"$/;
$MACROS{$lhs} = ${rhs};
print "##M: $lhs = <<$MACROS{$lhs}>>\n" if $opt{d};
}
else
{
print "Expand: $line\n" if $opt{d};
$line = macro_expand($line);
print "$line\n";
}
}
Given a configuration file, cfg, containing:
LINE_D="(L#'id' == 'log') AND L#'id' IS NULL"
and another file, F, containing:
PRINT '$LINE_D'
PRINT '${LINE_D}'
the output of perl setmacro.pl cfg F is:
PRINT '(L#'id' == 'log') AND L#'id' IS NULL'
PRINT '(L#'id' == 'log') AND L#'id' IS NULL'
This matches the required output, but gives me the heebie-jeebies with its multiple single quotes. However, the customer is always right!
(I think I got rid of the residual Perl 4-isms; the base script still had a few remnants left over, and some comments about how Perl 5.001 handles things differently. It does use $` and $' which is generally not a good idea. However it works, so fixing that is an exercise for the reader. The regex variable is not now necessary; it was when it was also recognizing make macro notations — $(macro) as well as ${macro}.)

Use of uninitialized value in a Perl script

I have the below program for checking the file availability in a Unix directory.
my $numbera = "c://";
my $numberb = "test1.txt";
check_file_exist($numbera, $numberb);
sub check_file_exist {
my $download_filename;
my ($numbera,$numberb) = #_;
$download_filename = $numbera.$numberb;
print "*** $download_filename ****";
my $mtime = (stat $download_filename)[9];
my $filedatetime = scalar localtime $mtime;
if (-e $download_filename) {
print "Data File Exist which is created on $filedatetime";
}
unless (-e $download_filename) {
print "File not exists";
}
}
while running the program I am getting the below error:
*** data_file=HASH(0xa912f0)/home1/saravanan/ ****
Use of uninitialized value in localtime at /home1/saravanan/data_file.pl
First, always put these in your program:
use strict;
use warnings;
When you use strict, you will have to declare your variables with either my or our (HINT: You use my about 99.99% of the time).
These will catch all sorts of errors in your script:
Also, use indentations. It makes your script easier to read. It is also bad form to output inside of your subroutine (unless that is the purpose of your subroutine. Instead, have your subroutine return (or not return a value), and then display that.
Your problem is that you were attempting to stat a file before you knew whether it exists or not. You need to put your stat inside your if statement where you check for the file's existence.
I've made a few changes besides what I stated above:
I use say instead of print. If you use print, you have to put in a terminating \n. The say command does this for you.
I pull in my parameters as soon as I get the subroutine (and use better variable names than $numbera and $numberb.
I use if/then/else instead of doing an if and then an unless with the same test. I no longer use unless in most circumstances. It's simply clearer to say if ( not ... ).
The subroutine either returns a datestamp or returns nothing. I check for the return value of the subroutine with my if statement.
Here's your program updated a bit:
use warnings;
use strict;
use autodie;
use feature qw(say);
use Data::Dumper;
my $numbera = "/Users/david";
my $numberb = ".profile";
if ( my $timestamp = check_file_exist( $numbera, $numberb ) ) {
say "The file was downloaded at $timestamp";
}
else {
say "The file does not exist";
}
sub check_file_exist {
my $directory = shift;
my $file_name = shift;
my $download_filename = "$directory/$file_name";
my #stat = stat($download_filename);
if (not #stat) {
return;
}
my $mtime = $stat[9];
return scalar localtime $mtime;
}

Resources