Perl assignment with a dummy placeholder - perl

In other languages I've used like Erlang and Python, if I am splitting a string and don't care about one of the fields, I can use an underscore placeholder. I tried this in Perl:
(_,$id) = split('=',$fields[1]);
But I get the following error:
Can't modify constant item in list assignment at ./generate_datasets.pl line 17, near ");"
Execution of ./generate_datasets.pl aborted due to compilation errors.
Does Perl have a similar such pattern that I could use instead of creating a useless temporary variables?

undef serves the same purpose in Perl.
(undef, $something, $otherthing) = split(' ', $str);

You don't even need placeholders if you use Slices:
use warnings;
use strict;
my ($id) = (split /=/, 'foo=id123')[1];
print "$id\n";
__END__
id123

You can assign to (undef).
(undef, my $id) = split(/=/, $fields[1]);
You can even use my (undef).
my (undef, $id) = split(/=/, $fields[1]);
You could also use a list slice.
my $id = ( split(/=/, $fields[1]) )[1];

And just to explain why you get the particular error that you see...
_ is a internal Perl variable that can be used in the stat command to indicate "the same file as we used in the previous stat call". That way Perl uses a cached stat data structure and doesn't make another stat call.
if (-x $file and -r _) { ... }
This filehandle is a constant value and can't be written to. The variable is stored in the same typeglob as $_ and #_.
See perldoc stat.

Related

Perl: passing hash by ref using rule1

I am still unclear about why by ref portion is showing undefined value for %Q and $_ uninitialized. I have been looking through perlreftut and still unable to see what I have done wrong. Passing the hash as a flat array has no issue.
Doing it by ref with testRef(\%mkPara) passes a scalar hash reference to the subroutine, right? So, does my %Q = %{$_} not turn it back into a hash?
use strict;
use diagnostics;
use warnings;
my %mkPara = ('aa'=>2,'bb'=>3,'cc'=>4,'dd'=>5);
sub testFlat
{
my %P = #_;
print "$P{'aa'}, $P{'bb'}, ", $P{'cc'}*$P{'dd'}, "\n";
}
sub testRef
{
my %Q = %{$_}; #can't use an undefined value as HASH reference
#print $_->{'aa'}, "\n";#Use of uninitialized value
print $Q{'aa'},"\n";
}
#testFlat(%mkPara);
testRef(\%mkPara);
When you use arguments in a function call (\%mkPara in your case), you can access them through #_ array inside the function.
Here, you pass a single argument to the function : \%mkPara, which you can then access by accessing the first element of #_ by using $_[0].
$_ is the default variable for some builtin functions/operators (print, m//, s///, chomp and a lot more). Usually seen in while or for loops. But in your code, you have no reason to use it (you are never setting it to anything, so it's still set to undef, hence the error "Can't use an undefined value as a HASH reference".
So your function should actually be :
sub testRef
{
my %Q = %{$_[0]}; # instead of %{$_}
print $_[0]->{'aa'}, "\n"; # instead of $_->{'aa'}
print $Q{'aa'},"\n";
}
If needed, you can find more about functions on perlsub.
However, as #Ikegami pointed out in the comments, using my %Q = %{$_[0]}; creates a copy of the hash you sent to the function, which in most cases (including that one where you just print a key of the hash) is very suboptimal as you could just use a hashref (like you are doing when you do $_[0]->{'aa'}).
You can use hash references like this (roughly the same example as the answer of #Zaid) :
sub testRef
{
my ( $Q ) = #_;
print $Q->{aa} ;
print $_, "\n" for keys %$Q;
}
testRef(\%mkPara);
There are quite a lot of resources about references online, for instance perlreftut that you were already looking at.
This can seem a bit tricky at first, but the reason is that $_ is not the same as #_.
From perlvar:
$_ is the implicit/"default" variable that does not have to be spelled out explicitly for certain functions (e.g. split )
Within a subroutine the array #_ contains the parameters passed to that subroutine
So the reason why
my %Q = %{$_};
says you can't use an undefined value as hash reference is because $_ is not defined.
What you really need here is
my %Q = %{$_[0]};
because that is the first element of #_, which is what was passed to testRef in the first place.
In practice I tend to find myself doing things a little differently because it lends itself to flexibility for future modifications:
sub testRef {
my ( $Q ) = #_;
print $_, "\n" for keys %$Q; # just as an example
}

Not an ARRAY reference error in "pop($str)"

I am learning Perl for work and I'm trying to practise with some basic programs.
I want my program to take a string from STDIN and modify it by taking the last character and putting it at the start of the string.
I get an error when I use variable $str in $str = <STDIN>.
Here is my code:
my $str = "\0";
$str = <STDIN>;
sub last_to_first {
chomp($str);
pop($str);
print $str;
}
last_to_first;
Exec :
Matrix :hi
Not an ARRAY reference at matrix.pl line 13, <STDIN> line 1.
Why your approach doesn't work
The pop keyword does not work on strings. Strings in Perl are not automatically cast to character arrays, and those array keywords only work on arrays.
The error message is Not an ARRAY reference because pop sees a scalar variable. References are scalars in Perl (the scalar here is something like a reference to the address of the actual array in memory). The pop built-in takes array references in Perl versions between 5.14 and 5.22. It was experimental, but got removed in the (currently latest) 5.24.
Starting with Perl 5.14, an experimental feature allowed pop to take a scalar expression. This experiment has been deemed unsuccessful, and was removed as of Perl 5.24.
How to make it work
You have to split and join your string first.
my $str = 'foo';
# turn it into an array
my #chars = split //, $str;
# remove the last char and put it at the front
unshift #chars, pop #chars;
# turn it back into a string
$str = join '', #chars;
print $str;
That will give you ofo.
Now to use that as a sub, you should pass a parameter. Otherwise you do not need a subroutine.
sub last_to_first {
my $str = shift;
my #chars = split //, $str;
unshift #chars, pop #chars;
$str = join '', #chars;
return $str;
}
You can call that sub with any string argument. You should do the chomp to remove the trailing newline from STDIN outside of the sub, because it is not needed for switching the chars. Always build your subs in the smallest possible unit to make it easy to debug them. One piece of code should do exactly one functionality.
You also do not need to initialize a string with \0. In fact, that doesn't make sense.
Here's a full program.
use strict;
use warnings 'all';
my $str = <STDIN>;
chomp $str;
print last_to_first($str);
sub last_to_first {
my $str = shift;
my #chars = split //, $str;
unshift #chars, pop #chars;
$str = join '', #chars;
return $str;
}
Testing your program
Because you now have one unit in your last_to_first function, you can easily implement a unit test. Perl brings Test::Simple and Test::More (and other tools) for that purpose. Because this is simple, we'll go with Test::Simple.
You load it, tell it how many tests you are going to do, and then use the ok function. Ideally you would put the stuff you want to test into its own module, but for simplicity I'll have it all in the same program.
use strict;
use warnings 'all';
use Test::Simple tests => 3;
ok last_to_first('foo', 'ofo');
ok last_to_first('123', '321');
ok last_to_first('qqqqqq', 'qqqqqq');
sub last_to_first {
my $str = shift;
my #chars = split //, $str;
unshift #chars, pop #chars;
$str = join '', #chars;
return $str;
}
This will output the following:
1..3
ok 1
ok 2
ok 3
Run it with prove instead of perl to get a bit more comprehensive output.
Refactoring it
Now let's change the implementation of last_to_first to use a regular expression substitution with s/// instead of the array approach.
sub last_to_first {
my $str = shift;
$str =~ s/^(.+)(.)$/$2$1/;
return $str;
}
This code uses a pattern match with two groups (). The first one has a lot of chars after the beginning of the string ^, and the second one has exactly one char, after which the string ends $. You can check it out here. Those groups end up in $1 and $2, and all we need to do is switch them around.
If you replace your function in the program with the test, and then run it, the output will be the same. You have just refactored one of the units in your program.
You can also try the substr approach from zdim's answer with this test, and you will see that the tests still pass.
The core function pop takes an array, and removes and returns its last element.
To manipulate characters in a string you can use substr, for example
use warnings;
use strict;
my $str = <STDIN>;
chomp($str);
my $last_char = substr $str, -1, 1, '';
my $new_str = $last_char . $str;
The arguments to substr mean: search the variable $str, at offset -1 (one from the back), for a substring of length 1, and replace that with an empty string '' (thus removing it). The substring that is found, here the last character, is returned. See the documentation page linked above.
In the last line the returned character is concatenated with the remaining string, using the . operator.
You can browse the list of functions broken down by categories at Perl functions by category.
Perl documentation has a lot of goodies, please look around.
Strings are very often manipulated using regular expressions. See the tutorial perlretut, the quick start perlrequick, the quick reference perlreref, and the full reference perlre.
You can also split a string into a character array and work with that. This is shown in detail in the answer by simbabque, which packs a whole lot more of good advice.
This is for substring function used for array variables:
my #arrays = qw(jan feb mar);
last_to_first(#arrays);
sub last_to_first
{
my #lists = #_;
my $last = pop(#lists);
#print $last;
unshift #lists, $last;
print #lists;
}
This is for substring function used for scalar variables:
my $str = "";
$str = <STDIN>;
chomp ($str);
last_to_first($str);
sub last_to_first
{
my $chr = shift;
my $lastchar = substr($chr, -1);
print $lastchar;
}

Perl map function not working

i have a problem. This perl program should open all files and map them together, something similiar to paste command in unix system.
my #files;
for (#fileList ? #fileList : qw(-)) {
open $files[#files], '<', $_; #}
}
while (grep defined, (my #lines = map {scalar <$_>;} #files)) {
chomp #lines;
print join("\t", #lines), "\n";
}
The problem is that, when it comes to two different files like
One
Two
Three
And:
Apple
Banana
Orange
Kiwi
It throws me an error of uninitialized value.
Use of uninitialized value $lines[0] in chomp
Use of uninitialized value $lines[0] in join
Also same error vice versa, when files are Apple,Banana.. and One two three.
Thank you in advance
The problem is that since your files have a different length, you will get some undefined values in your #lines array. You cannot filter them out in the while condition, but you can filter them out afterwards, inside the loop:
while (grep defined, (my #lines = map {scalar <$_>;} #files)) {
#lines = map defined($_) ? $_ : "", #lines;
This will replace undefined values with the empty string, removing any uninitialized warnings for those values, while keeping your tabs correctly aligned.
Note that your code actually does what it is meant to do, because undef will stringify to the empty string. It is only because you have warnings on that you get the warnings about it. However, it is a very good idea to have warnings on, so errors such as this can be found and fixed properly.
You should note that using open without checking its return value is not a good idea, unless you explicitly handle the failed cases. The proper way is to do this:
open $files[#files], '<', $_ or die $!;
For simplicity one can also use
use autodie;
The autodie module will produce fatal errors for critical function calls such as open.
If you have Perl v5.10 or later installed (and you really should, as it is five years old now) then you can use the // (defined-or) operator to substitute a null string for undef after the end of the files. It will also impose scalar context on its operands, so there is no need to use the scalar operator. In addition you must substitute the length operator for defined, resulting in this code
while (grep length, (my #lines = map { <$_> // '' } #files)) {
chomp #lines;
print join("\t", #lines), "\n";
}

How to print variables in Perl

I have some code that looks like
my ($ids,$nIds);
while (<myFile>){
chomp;
$ids.= $_ . " ";
$nIds++;
}
This should concatenate every line in my myFile, and nIds should be my number of lines. How do I print out my $ids and $nIds?
I tried simply print $ids, but Perl complains.
my ($ids, $nIds)
is a list, right? With two elements?
print "Number of lines: $nids\n";
print "Content: $ids\n";
How did Perl complain? print $ids should work, though you probably want a newline at the end, either explicitly with print as above or implicitly by using say or -l/$\.
If you want to interpolate a variable in a string and have something immediately after it that would looks like part of the variable but isn't, enclose the variable name in {}:
print "foo${ids}bar";
You should always include all relevant code when asking a question. In this case, the print statement that is the center of your question. The print statement is probably the most crucial piece of information. The second most crucial piece of information is the error, which you also did not include. Next time, include both of those.
print $ids should be a fairly hard statement to mess up, but it is possible. Possible reasons:
$ids is undefined. Gives the warning undefined value in print
$ids is out of scope. With use
strict, gives fatal warning Global
variable $ids needs explicit package
name, and otherwise the undefined
warning from above.
You forgot a semi-colon at the end of
the line.
You tried to do print $ids $nIds,
in which case perl thinks that $ids
is supposed to be a filehandle, and
you get an error such as print to
unopened filehandle.
Explanations
1: Should not happen. It might happen if you do something like this (assuming you are not using strict):
my $var;
while (<>) {
$Var .= $_;
}
print $var;
Gives the warning for undefined value, because $Var and $var are two different variables.
2: Might happen, if you do something like this:
if ($something) {
my $var = "something happened!";
}
print $var;
my declares the variable inside the current block. Outside the block, it is out of scope.
3: Simple enough, common mistake, easily fixed. Easier to spot with use warnings.
4: Also a common mistake. There are a number of ways to correctly print two variables in the same print statement:
print "$var1 $var2"; # concatenation inside a double quoted string
print $var1 . $var2; # concatenation
print $var1, $var2; # supplying print with a list of args
Lastly, some perl magic tips for you:
use strict;
use warnings;
# open with explicit direction '<', check the return value
# to make sure open succeeded. Using a lexical filehandle.
open my $fh, '<', 'file.txt' or die $!;
# read the whole file into an array and
# chomp all the lines at once
chomp(my #file = <$fh>);
close $fh;
my $ids = join(' ', #file);
my $nIds = scalar #file;
print "Number of lines: $nIds\n";
print "Text:\n$ids\n";
Reading the whole file into an array is suitable for small files only, otherwise it uses a lot of memory. Usually, line-by-line is preferred.
Variations:
print "#file" is equivalent to
$ids = join(' ',#file); print $ids;
$#file will return the last index
in #file. Since arrays usually start at 0,
$#file + 1 is equivalent to scalar #file.
You can also do:
my $ids;
do {
local $/;
$ids = <$fh>;
}
By temporarily "turning off" $/, the input record separator, i.e. newline, you will make <$fh> return the entire file. What <$fh> really does is read until it finds $/, then return that string. Note that this will preserve the newlines in $ids.
Line-by-line solution:
open my $fh, '<', 'file.txt' or die $!; # btw, $! contains the most recent error
my $ids;
while (<$fh>) {
chomp;
$ids .= "$_ "; # concatenate with string
}
my $nIds = $.; # $. is Current line number for the last filehandle accessed.
How do I print out my $ids and $nIds?
print "$ids\n";
print "$nIds\n";
I tried simply print $ids, but Perl complains.
Complains about what? Uninitialised value? Perhaps your loop was never entered due to an error opening the file. Be sure to check if open returned an error, and make sure you are using use strict; use warnings;.
my ($ids, $nIds) is a list, right? With two elements?
It's a (very special) function call. $ids,$nIds is a list with two elements.

How can I pass command-line arguments to a Perl program?

I'm working on a Perl script. How can I pass command line parameters to it?
Example:
script.pl "string1" "string2"
Depends on what you want to do. If you want to use the two arguments as input files, you can just pass them in and then use <> to read their contents.
If they have a different meaning, you can use GetOpt::Std and GetOpt::Long to process them easily. GetOpt::Std supports only single-character switches and GetOpt::Long is much more flexible. From GetOpt::Long:
use Getopt::Long;
my $data = "file.dat";
my $length = 24;
my $verbose;
$result = GetOptions ("length=i" => \$length, # numeric
"file=s" => \$data, # string
"verbose" => \$verbose); # flag
Alternatively, #ARGV is a special variable that contains all the command line arguments. $ARGV[0] is the first (ie. "string1" in your case) and $ARGV[1] is the second argument. You don't need a special module to access #ARGV.
You pass them in just like you're thinking, and in your script, you get them from the array #ARGV. Like so:
my $numArgs = $#ARGV + 1;
print "thanks, you gave me $numArgs command-line arguments.\n";
foreach my $argnum (0 .. $#ARGV) {
print "$ARGV[$argnum]\n";
}
From here.
foreach my $arg (#ARGV) {
print $arg, "\n";
}
will print each argument.
Yet another options is to use perl -s, eg:
#!/usr/bin/perl -s
print "value of -x: $x\n";
print "value of -name: $name\n";
Then call it like this :
% ./myprog -x -name=Jeff
value of -x: 1
value of -name: Jeff
Or see the original article for more details:
Alternatively, a sexier perlish way.....
my ($src, $dest) = #ARGV;
"Assumes" two values are passed. Extra code can verify the assumption is safe.
You can access them directly, by assigning the special variable #ARGV to a list of variables.
So, for example:
( $st, $prod, $ar, $file, $chart, $e, $max, $flag ,$id) = #ARGV;
perl tmp.pl 1 2 3 4 5
If the arguments are filenames to be read from, use the diamond (<>) operator to get at their contents:
while (my $line = <>) {
process_line($line);
}
If the arguments are options/switches, use GetOpt::Std or GetOpt::Long, as already shown by slavy13.myopenid.com.
On the off chance that they're something else, you can access them either by walking through #ARGV explicitly or with the shift command:
while (my $arg = shift) {
print "Found argument $arg\n";
}
(Note that doing this with shift will only work if you are outside of all subs. Within a sub, it will retrieve the list of arguments passed to the sub rather than those passed to the program.)
my $output_file;
if((scalar (#ARGV) == 2) && ($ARGV[0] eq "-i"))
{
$output_file= chomp($ARGV[1]) ;
}
If you just want some values, you can just use the #ARGV array. But if you are looking for something more powerful in order to do some command line options processing, you should use Getopt::Long.