I have a data file, with each line holding one number. I am trying to read this file into an array. Here is the script I wrote:
#!/usr/bin/perl -w
$file1 = '/home/usr1/test.list';
open(FILEC, $file1);
my #cArray = FILEC;
close FILEC;
print #cArray;
But after executing this file, nothing was printed out? I have checked the input, test.list, which is at the correct location. What may be the reason?
You're missing the <>(line) operator:
my #cArray = <FILEC>;
ought to help.
FatalError is correct, you need a readline operator. You can read more about <> in perldoc perlop and more about the readline function in perldoc -f readline.
Once you have that knowledge, you can see why the following could also work (though perhaps not recommended for readability). Also I will use Data::Dumper to print a better representation of #cArray.
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
#ARGV = qw( /home/usr1/test.list );
# or remove previous line and call script as
# script.pl /home/usr1/test.list
my #cArray = <>;
print Dumper \#cArray;
Some further notes: a more modern version of your would:
use the three argument form of open
check that open succeeds
use a lexical rather than bareword handle
use strict as well as use warnings (rather than -w)
giving
#!/usr/bin/env perl
use strict;
use warnings;
my $file1 = '/home/usr1/test.list';
open(my $handle, '<', $file1)
or die "Could not open $file1: $!";
my #cArray = <$handle>;
print #cArray;
Related
I want to replace the text in the file and overwrite file.
use strict;
use warnings;
my ($str1, $str2, $i, $all_line);
$str1 = "AAA";
$str2 = "bbb";
open(RP, "+>", "testfile") ;
$all_line = $_;
$i = 0;
while(<RP>) {
while(/$str1/) {
$i++;
}
s/$str1/$str2/g;
print RP $_;
}
close(RP);
A normal process is to read the file line by line and write out each line, changed as/if needed, to a new file. Once that's all done rename that new file, atomically as much as possible, so to overwrite the orgiinal.
Here is an example of a library that does all that for us, Path::Tiny
use warnings;
use strict;
use feature 'say';
use Path::Tiny;
my $file = shift || die "Usage: $0 file\n";
say "File to process:";
say path($file)->slurp;
# NOTE: This CHANGES THE INPUT FILE
#
# Process each line: upper-case a letter after .
path($file)->edit_lines( sub { s/\.\s+\K([a-z])/\U$1/g } );
say "File now:";
say path($file)->slurp;
This upper-cases a letter following a dot (period), after some spaces, on each line where it is found and copies all other lines unchanged. (It's just an example, not a proper linguistic fix.)
Note: the input file is edited in-place so it will have been changed after this is done.
This capability was introduced in the module's version 0.077, of 2016-02-10. (For reference, Perl version 5.24 came in 2016-08. So with the system Perl 5.24 or newer, a Path::Tiny installed from an OS package or as a default CPAN version should have this method.)
Perl has a built-in in-place editing facility: The -i command line switch. You can access the functionality of this switch via $^I.
use strict;
use warnings;
my $str1 = "AAA";
my $str2 = "bbb";
local $^I = ''; # Same as `perl -i`. For `-i~`, assign `"~"`.
local #ARGV = "testfile";
while (<>) {
s/\Q$str1/$str2/g;
print;
}
I was not going to leave an answer, but I discovered when leaving a comment that I did have some feedback for you, so here goes.
open(RP, "+>", "testfile") ;
The mode "+>" will truncate your file, delete all it's content. This is described in the documentation for open:
You can put a + in front of the > or < to indicate that you want both read and write access to the file; thus +< is almost always preferred for read/write updates--the +> mode would clobber the file first. You can't usually use either read-write mode for updating textfiles, since they have variable-length records. See the -i switch in perlrun for a better approach.
So naturally, you can't read from the file if you first delete it's content. They mention here the -i switch, which is described like this in perl -h:
-i[extension] edit <> files in place (makes backup if extension supplied)
This is what ikegami's answer describes, only in his case it is done from within a program file rather than on the command line.
But, using the + mode for both reading and writing is not really a good way to do it. Basically it becomes difficult to print where you want to print. The recommended way is to instead read from one file, and then print to another. After the editing is done, you can rename and delete files as required. And this is exactly what the -i switch does for you. It is a predefined functionality of Perl. Read more about it in perldoc perlrun.
Next, you should use a lexical file handle. E.g. my $fh, instead of a global. And you should also check the return value from the open, to make sure there was not an error. Which gives us:
open my $fh, "<", "testfile" or die "Cannot open 'testfile': $!";
Usually if open fails, you want the program to die, and report the reason it failed. The error is in the $! variable.
Another thing to note is that you should not declare all your variables at the top of the file. It is good that you use use strict; use warnings, Perl code should never be written without them, but this is not how you handle it. You declare your variable as close to the place you use the variable as possible, and in the smallest scope possible. With a my declared variable, that is the nearest enclosing block { ... }. This will make it easy to trace your variable in bigger programs, and it will encapsulate your code and protect your variable.
In your case, you would simply put the my before all the variables, like so:
my $str1 = "AAA";
my $str2 = "bbb";
my $all_line = $_;
my $i = 0;
Note that $_ will be empty/undefined there, so that assignment is kind of pointless. If your intent was to use $all_line as the loop variable, you would do:
while (my $all_line = <$fh>) {
Note that this variable is declared in the smallest scope possible, and we are using a lexical file handle $fh.
Another important note is that your replacement strings can contain regex meta characters. Sometimes you want them to be included, like for example:
my $str1 = "a+"; # match one or more 'a'
Sometimes you don't want that:
my $str1 = "google.com"; # '.' is meant as a period, not the "match anything" character
I will assume that you most often do not want that, in which case you should use the escape sequence \Q ... \E which disables regex meta characters inside it.
So what do we get if we put all this together? Well, you might get something like this:
use strict;
use warnings;
my $str1 = "AAA";
my $str2 = "bbb";
my $filename = shift || "testfile"; # 'testfile', or whatever the program argument is
open my $fh_in, "<", $filename or die "Cannot open '$filename': $!";
open my $fh_out, ">", "$filename.out" or die "Cannot open '$filename.out': $!";
while (<$fh_in>) { # read from input file...
s/\Q$str1\E/$str2/g; # perform substitution...
print $fh_out $_; # print to output file
}
close $fh_in;
close $fh_out;
After this finishes, you may choose to rename the files and delete one or the other. This is basically the same procedure as using the -i switch, except here we do it explicitly.
rename $filename, "$filename.bak"; # save backup of input file in .bak extension
rename "$filename.out", $filename; # clobber the input file
Renaming files is sometimes also facilitated by the File::Copy module, which is a core module.
With all this said, you can replace all your code with this:
perl -i -pe's/AAA/bbb/g' testfile
And this is the power of the -i switch.
I am looking for a Perl snippet to print the number 100, contained in a C header, to the terminal.
#ifndef VERSIONS_H
#define VERSIONS_H
#define __SOFTWARE_REVISION__ 100
#endif
I am able to extract the #define line, but the number.
#!perl -w
use warnings;
use strict;
my $line;
my $file = shift #ARGV;
open my $fh, "<", $file or die $!;
while (<$fh>) {
print if /__SOFTWARE_REVISION__/;
}
close ($fh);
The regex matches for any line that contains that literal phrase, not only where it is defined. You need to specify the line far more precisely, and to capture the needed number.
The code also proceeds to loop through the file needlessly.
With a few more adjustments
use warnings;
use strict;
use feature qw(say);
my $file = shift #ARGV;
die "Usage: $0 filename\n" if not $file or not -f $file;
open my $fh, '<', $file or die "Can't open $file: $!";
while (<$fh>) {
if (/^\s*#\s*define\s+__SOFTWARE_REVISION__\s+([0-9]+)/) {
say "Software revision: $1";
last;
}
}
close $fh;
If the revision can be other than integer replace [0-9]+ with a the pattern [0-9.]+ that allows a period, or [0-9.a-z] if (low-case) letters are allowed in versioning ... or simply \S+.
I'd suggest to work through the tutorial perlretut with reference perlre on hand.
I assume that this need comes up in a Perl program (and not that Perl is the chosen tool for this).
Still, note that there are other, more systemic, ways to retrieve this information using the compiler (see a comment on gcc by rici for example).
While it is better to use Perl in a Perl script rather than go out to the system, this case may be one of exceptions: it may be better to use an external command rather than parse source files, a task well known to be treacherous.
Here's an approach using regex to capture the version number if present in a line. Note that parenthesis in the regex delimit the capturing group and the variable $1 is used to access the capture.
#!perl -w
use warnings;
use strict;
my $file = shift #ARGV;
open my $fh, "<", $file or die $!;
while (<$fh>) {
print "$1\n" if /^#define +__SOFTWARE_REVISION__ +(\d+)$/;
}
close $fh;
Output:
100
The regex is more selective than yours, which matches any instance of __SOFTWARE_REVISION__ in a line. This regex matches #define at the beginning of line, followed by one or more spaces, followed by __SOFTWARE_REVISION__, followed by one or more spaces, followed by a one or more digit capturing group at the end of the line.
#!/usr/bin/perl
$test ="#test=hello"; #(Actually am reading this text from File and assigning to a variable)
print "$test";
After executing the above code perl outputs the below
Output:
=hello instead of #test=hello.
Can anyone please explain the above. I believe that it is considering as empty array, but how can i avoid this when and reading a file, please clear my misconception.
Thanks,
Perl interpolates variables in strings delimited by double quotes.
#test is treated as an array.
Unless you have created this explicitly, you should get an error when you try to do that. If you don't then you must have forgotten to use strict; and use warnings;!
Use single quoted strings if you don't want to interpolate variables.
#!/usr/bin/env perl
use strict;
use warnings;
use v5.10;
my $test = '#test=hello';
say $test;
This question already has answers here:
How do I efficiently parse a CSV file in Perl?
(6 answers)
Closed 7 years ago.
I need to get a specific field from a CSV file and put it in an array. I am not sure how to do this. This is what I have tried so far.
#!/usr/bin/perl
use strict;
use warnings;
my #array = <>;
my #fields = split ",", #array;
print #fields[2];
This is an example of the CSV file
9988,Kathleen,Brown,kbrownc#goo.gl,OH,Female,Italian
9989,Antonio,Ford,afordb#bigcartel.com,IL,Male,
9990,Diana,Banks,dbanksa#jalbum.net,MA,Female,English
If there is any chance that your CSV file contains quoted fields (so that each field may itself contain a comma) then you should use Text::CSV to handle the data properly. However, for simple data like that in your question, it is fine to use just split.
Your code would look something like this. Note that it is usually unnecessary to read an entire file into memory, and line-by-line processing is more memory-efficient. It also tends to focus the programmer's attention on a single line and hence improve the resulting design.
use strict;
use warnings;
my #names;
while ( <> ) {
chomp;
my #fields = split /,/;
push #names, $fields[2];
}
print "$_\n" for #names;
output
Brown
Ford
Banks
Update
If you are comfortable with map then you may prefer this. It is much more concise, but suffers from the same inefficiency as your own code in that it reads the whole file into memory at once (although it discards it again immediately). Unless the file is enormous that shouldn't be a problem.
use strict;
use warnings;
my #names = map { chomp; ( split /,/ )[2]; } <>;
print "$_\n" for #names;
There is a perl module that handles many file formats including csv. You can install the module by running:
$ sudo cpan install Text::CSV;
Now you'll be able to easily have the needed parsing of your comma delimiter (which is the default) or specify any other character.
After installing the perl module, this is a quick script to achieve your task. I created a text file with your data called test.csv.
#!/usr/bin/perl
use strict;
use warnings;
require Text::CSV;
my $csv = Text::CSV->new;
open (DATA, "<test.csv") or die "Can't open file...";
while (<DATA>) {
$csv->parse($_);
my#fields = $csv->fields();
print $fields[2];
}
close DATA;
You can see other features of the Text::CSV module reviewing the documentation by running:
$ perldoc Text::CSV
my below code is very simple... all im doing is grepping a file using an IP address REGEX, and any IP addresses that are found that match my REGEX, I want to store them in #array2.
i know my REGEX works, because ive tested it on a linux command line and it works exactly how I want, but when its integrated into the script, it just returns blank. there should be 700 IP's stored in the array.
#!/usr/bin/perl
use warnings;
use strict;
my #array2 = `grep -Eo "\"\b[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\"" test1.txt`;
print #array2;
Backticks `` behave like a double quoted string by default.
Therefore you need to escape your backslashes:
my #array2 = `grep -Eo "\\"\\b[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\"" test1.txt`;
Alternatively, you can use a single quoted version of qx to avoid any interpolation:
my #array2 = qx'grep -Eo "\"\b[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\"" test1.txt';
However, the method I'd recommend is to not shell out at all, but instead do this logic in perl:
my #array2 = do {
open my $fh, '<', 'test1.txt' or die "Can't open file: $!";
grep /\b[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\b/, <$fh>;
};
I really wouldn't mix bash and perl. It's just asking for pain. Perl can do it all natively.
Something like:
open (my $input_fh, "<", "test.txt" ) or die $!;
my #results = grep ( /\b[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/, <$input_fh> );
This does however, require slurping the file into memory, which isn't optimal - I'd generally use a while loop, instead.
The text inside the backticks undergoes double-quotish substitution. You will need to double your backslashes.
Running grep from inside Perl is dubious, anyway; just slurp in the text file and use Perl to find the matches.
The easiest way to retrieve the output from an external command is to use open():
open(FH, 'grep -Eo \"\b[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\" test1.txt'."|")
my #array2=<FH>;
close (FH);
..though I think Sobrique's idea is the best answer here.