This question already has answers here:
Why is 'print (52-80)*42' different than 'print 42*(52-80)' in Perl?
(3 answers)
Closed 7 years ago.
Recently I came across a statement like this
print +( map { $_ + 1 } #$_ ), "\n" for #$array; # <- AoA
I had not seen the + operator used with print like this before. Running the statement, it is not hard to infer what it is doing. But I am having trouble finding documentation on the + operator used this way.
my #tab = (
[1,2,3,4],
[qw(a b c d)],
);
my $test = "(", +( map { qq( >>$_<< ) } #$_ ), ")\n" for #tab;
print $test;
my #test = "(", +( map { qq( >>$_<< ) } #$_ ), ")\n" for #tab;
print #test;
my %test = "(", +( map { qq( >>$_<< ) } #$_ ), ")\n" for #tab;
print %test;
Above produces the warnings: Useless use of a constant ()) in void context for all three tests. Use of uninitialized value for the scalar test and Odd number of elements in hash assignment for both the array and hash tests, It would be nice to be able to store the statements output in a scalar. I know I can store an Identical string in a scalar using a for loop like below.
print "(", +( map { qq( >>$_<< ) } #$_ ), ")\n" for #tab;
my $out = '';
for (#tab) {
$out .= "(" . ( join '', map { qq( >>$_<< ) } #$_ ) . ")\n";
}
print $out;
# output ...
( >>1<< >>2<< >>3<< >>4<< )
( >>a<< >>b<< >>c<< >>d<< )
---
( >>1<< >>2<< >>3<< >>4<< )
( >>a<< >>b<< >>c<< >>d<< )
Why can't I store the statement in any variable or test its type. Id like in detail what is actually happening with the print builtin when using the + operator.
EDIT: I belive my original post was kind of confusing, I did want to learn more about the + operator used with print but what I was really after is how to store each iteration of the shorthand for statement into a scalar string - I found a way after some testing ...
use strict;
use warnings;
my #a = ([1,2,3],[qw/a b c/]);
my $one = '';
$one .= "(" . ( join '', map { " >>$_<< " } #$_ ) . ")\n" for #a;
print $one;
# output ...
( >>1<< >>2<< >>3<< )
( >>a<< >>b<< >>c<< )
I think you've asked two questions here, so I'll answer both of them as well as I can...
Firstly, you're referring to the unary "+" operator, from perlop
Unary "+" has no effect whatsoever, even on strings. It is useful syntactically for separating a function name from a parenthesized expression that would otherwise be interpreted as the complete list of function arguments.
To expand on what exactly is going on in the print statement, I found the explanation on perlmaven to be pretty good.
The documentation explains that the + separates the print function from the parentheses ( and tells perl that these are not the parentheses wrapping the parameters of the print function.
That might satisfy you, but if you are further intersted you can use the B::Deparse module to ask perl how does it understand this code-snippet:
print +(stat $filename)[7];
We save that content in the plus.pl file and run perl -MO=Deparse plus.pl. The result is:
print((stat $filename)[7]);
Hope this is helpful!
Edit
Explaining in detail why you can't use the same command to print that sequence and assign it to a scalar is maybe outside of the scope of this question, but I'll attempt to briefly explain... with the note that if I'm wrong about something, I'd love a correction, I've come to understand how this works through writing code using this, not from reading the perl docs on its inner workings :)
With the print statement
print "(", +( map { qq( >>$_<< ) } #$_ ), ")\n" for #tab;
perl is interpreting it as
(print "(", +( map { qq( >>$_<< ) } #$_ ), ")\n") for #tab;
That is, it's grouping all of the stuff you want to print, and performing it for each element in #tab. However, with your scalar assignment
my $test = "(", +( map { qq( >>$_<< ) } #$_ ), ")\n" for #tab;
perl is performing the for #tab code on ")\n", thus your Useless use of a constant ()) in void context warning. To do what you want, you need to make it explicit what you want for'd
my $test;
( $test .= "(" . join ('', ( map { qq( >>$_<< ) } #$_)) . ")\n") for #tab;
print $test;
Should get you what you're looking for. Note the parenthesis around the entire assignment, and the omission of the unary "+" before the map, which is no longer necessary.
perldoc -f print:
Be careful not to follow the
print keyword with a left parenthesis unless you want the
corresponding right parenthesis to terminate the arguments to the
print; put parentheses around all arguments (or interpose a "+",
but that doesn't look as good).
In this case, you do not want map to consume the "\n", you must put parentheses around the map { $_ + 1 } #$_. But, the moment you do that you run into the situation described above unless you put a + before the left parenthesis so that +(map { $_ + 1 } #$_) becomes a single argument to print instead of (map { $_ + 1 } #$_) being interpreted as the complete argument list.
If you had
print ( map { $_ + 1 } #$_ ), "\n" for #$array;
the comma following the right parenthesis above would be interpreted as the comma operator. print would just print the numbers, and not the newline:
$ perl -we '$array=[[1,2,3]];print ( map { $_ + 1 } #$_ ), "\n" for #$array;'
print (...) interpreted as function at -e line 1.
Useless use of a constant ("\n") in void context at -e line 1.
234%
The print is evaluated. As a side effect, it prints the numbers. The return value of the print is discarded, and the value of the expression becomes "\n". Since this value is not assigned to anything, you get the warning. Basically, the code ends up looking like:
"\n" for #$array;
except for the side effect of invoking print.
If you want to store the output of the loop in a variable, you can do:
my $string;
$string .= sprintf( "(%s)\n", join('', map {$_ + 1} #$_) ) for #tab;
Related
I'm practicing my Perl on codefights.
I've seen someone in a previous task feed Eval a list to make an array.
#a = map eval s/\B/*/gr, 0 .. 10000;
Yet, when i try and feed eval a list it give me an error
my ($a,$b,$c)=(1,2,3);
return 1 if eval "$a$_$b==$c", for qw "+ - / *";
This line also does not work as expected.
my ($a, $b, $c) = #_;
#aa=map eval "$a $_ $b", for qw "+ - / *";
However, this works
my ($a,$b,$c)=(1,2,3);
for (qw "+ - / *")
{
return 1 if eval "$a$_$b==$c";
}
Can someone explain what the difference between the statements is?
Thanks
You misunderstand what your first example does.
my #a = map eval s/\B/*/gr, 0 .. 10000;
This does not pass a list to eval. The eval s/...// is the argument to map in the non-block form. This becomes more clear when you add parentheses.
my #a = map( eval( s/\B/*/gr ), 0 .. 10000 );
It will call the eval statement for every number, and return a list of results, which gets assigned to #a. The code adds an asterisk after every other digit and builds a list of the resulting strings.
In this code, you again misunderstand what the map does.
my ($a, $b, $c) = #_;
#aa=map eval "$a $_ $b", for qw "+ - / *";
The map is essentially an in-place for loop. Adding a postfix for doesn't do anything useful here. map will already iterate all elements in your list. The list in this case is "+", "-", "/", "*", which was created by the qw// operator.
Get rid of the for and it will work.
my ( $x, $y ) = ( 1, 2 );
my #results = map eval "$x $_ $y", qw "+ - / *";
The results of this are:
3,
-1,
0.5,
2
I suggest you read up on map.
i want to make this 2-Commands shorter, 1 line if it possible.
my $id = shift;
my #splittedID = split(" ", $id);
return $splittedID[0];
but it should have at the end the same functional. Thanks
return (split " ", shift)[0];
Or, if you want:
(split " ", shift)[0];
(The result of the last line of a sub implicitly becomes the return value).
Even shorter (Perl 5.16 required for /r option):
$_[0]=~s/ .*//r
Of course, in actual production code, your original example is better, since it's readable.
Because you just want the first item of the split and ' ' is a special pattern that skips all leading spaces, a regex can also solve this like so:
sub firstword {
return (shift =~ /(\S+)/)[0];
}
my $x = firstword('asdf qwer jkl') # Equals 'asdf';
my $y = firstword(' qwer jkl') # Equals 'qwer';
my $z = firstword(' ') # Equals undef;
Also, the return keyword is optional of course, but shorter is not always better.
This is much more compact using a regular expression:
sub first_field {
return unless $_[0] =~ /(\S+)/;
$1;
}
As the title - please can anyone explain how the next scripts works
this prints the text: "Perl guys are smart"
''=~('(?{'.('])##^{'^'-[).*[').'"'.('-[)#{:__({:)[{(-:)^}'^'}>[,[]*&[[[[>[[#[[*_').',$/})')
this prints only "b"
use strict;
use warnings;
''=~('(?{'.('_/).+{'^'/]##_[').'"'.('=^'^'_|').',$/})')
the perl -MO=Deparse shows only this:
use warnings;
use strict 'refs';
'' =~ m[(?{print "b",$/})];
but havent any idea why... ;(
What is the recommended way decomposing like scripts? How to start?
so, tried this:
'' =~
(
'(?{'
.
(
'])##^{' ^ '-[).*['
)
.
'"'
.
(
'-[)#{:__({:)[{(-:)^}' ^ '}>[,[]*&[[[[>[[#[[*_'
)
.
',$/})'
)
several parts are concatenated by .. And the result of the bitwise ^ probably gives the text parts. The:
perl -e "print '-[)#{:__({:)[{(-:)^}' ^ '}>[,[]*&[[[[>[[#[[*_'"
prints "Perl guys are smart" and the first ^ generating "print".
But when, i rewrite it to:
'' =~
(
'(?{'
.
(
'print'
)
.
'"'
.
(
'Perl guys are smart'
)
.
',$/})'
)
My perl told me:
panic: top_env
Strange, first time i saw like error message...
Thats mean: it isn't allowed replace the 'str1' ^ 'str2' with the result, (don't understand why) and why the perl prints the panic message?
my perl:
This is perl 5, version 12, subversion 4 (v5.12.4) built for darwin-multi-2level
Ps: examples are generated here
In the line
.('_/).+{' ^ '/]##_[
when you evaluate ']' ^ '-', the result will be the letter p. ^ is a bitwise string operation, so after that we follow letter by letter to get result string.
Check my script, it works like your example. I hope it will help you.
use v5.14;
# actually we obfuscated print and your word + "
# it looks like that (print).'"'.(yor_word")
my $print = 'print';
my $string = 'special for stackoverflow by fxzuz"';
my $left = get_obfuscated($print);
my $right = get_obfuscated($string);
# prepare result regexp
my $result = "'' =~ ('(?{'.($left).'\"'.($right).',\$/})');";
say 'result obfuscated ' . $result;
eval $result;
sub get_obfuscated {
my $string = shift;
my #letters = split //, $string;
# all symbols like :,&? etc (exclude ' and \)
# we use them for obfuscation
my #array = (32..38, 40..47, 58..64, 91, 93..95, 123..126);
my $left_str = '';
my $right_str = '';
# obfuscated letter by letter
for my $letter (#letters) {
my #result;
# get right xor letters
for my $symbol (#array) {
# prepare xor results
my $result = ord $letter ^ $symbol;
push #result, { left => $result, right => $symbol } if $result ~~ #array;
}
my $rand_elem = $result[rand $#result];
$left_str .= chr $rand_elem->{left};
$right_str .= chr $rand_elem->{right};
}
my $obfuscated = "'$left_str' ^ '$right_str'";
say "$string => $obfuscated";
return $obfuscated;
}
The trick to understanding what's going on here is to look at the string being constructed by the XORs and concatenations:
(?{print "Perl guys are smart",$/})
This is an experimental regular expression feature of the form (?{ code }). So what you see printed to the terminal is the result of
print "Perl guys are smart",$/
being invoked by ''=~.... $/ is Perl's input record separator, which by default is a newline.
I have an input file that looks like
*firsttitle
nameA
nameB
nameC
*secondtitle
xnameA
xnameB
xnameC
I want to create a Perl script that takes this file and basically will create another perl script that looks like
#!/usr/bin/perl
use strict;
use warnings;
my %tags = (
"firsttitle" => [ qw (nameA nameB nameC) ],
"secondtitle" => [ qw (xnameA xnameB xnameC) ]);
my $rx = join '|', keys %tags;
while (<>) {
s/^\s*($rx):\s*(\d+)/$1: $tags{$1}[$2]/;
print;
}
My thought process is that I have to first match print out the regular perl code (#!,use..etc.).Then add " my%tags=(. Then take the input file and look for the * and that's the lookup for the hash and start parsing everything after until the next(*) or end of life. If it's another * then do it again. If it's EOF then add ");" and end. And then finish with printing the last bit of perl code. Help/ideas would be appreciated. If you're going to post code snippets could you go through and explain what each part is doing? Thanks!
Very simple script. First just parse through the input file. Lines that start with * will be titles, and all the following lines up until the next *-line will be values. We put this into a hash of arrays.
The map statement gives us a list of the hash key (the title), and it's values joined together with space. We put this in an array for printing. The printing itself is done with printf, which can be a bit difficult to use, since meta characters will mess us up. Any % that are to be literal must be written as %%. I also changed single quotes from the original to double quotes. I use single quotes on the printf pattern to avoid accidental interpolation of variables.
An alternative - possibly better one - is to not just printf at all, and simply concatenate the string in a normal fashion.
use strict;
use warnings;
my ($title, %hash);
while (<DATA>) {
chomp;
if (/^\*(.+)$/) {
$title = $1;
} else {
push #{$hash{$title}}, $_;
}
}
my #args = ( map { $_, join(' ', #{$hash{$_}}) } keys %hash );
printf '#!/usr/bin/perl
use strict;
use warnings;
my %%tags = (
"%s" => [ qw ( %s ) ],
"%s" => [ qw ( %s ) ]);
my $rx = join "|", keys %%tags;
while (<>) {
s/^\s*($rx):\s*(\d+)/$1: $tags{$1}[$2]/;
print;
}', #args;
__DATA__
*firsttitle
nameA
nameB
nameC
*secondtitle
xnameA
xnameB
xnameC
Update:
This will use a different method of printing, which will be more stable.
my #args = ( map { " '$_' => [ qw ( #{$hash{$_}} ) ],\n" } keys %hash );
print '#!/usr/bin/perl
use strict;
use warnings;
my %tags = (
', #args, '
);
my $rx = join "|", keys %tags;
while (<>) {
s/^\s*($rx):\s*(\d+)/$1: $tags{$1}[$2]/;
print;
}';
I have some simple Perl code:
#!/usr/bin/perl
use strict; # not in the OP, recommended
use warnings; # not in the OP, recommended
my $val = 1;
for ( 1 .. 100 ) {
$val = ($val * $val + 1) % 8051;
print ($val / 8050) . " \n";
}
But when I run it, the output is:
bash-3.2$ perl ./rand.pl
0.0002484472049689440.000621118012422360.003229813664596270.08409937888198760.92
... <snipped for brevity> ...
2919250.9284472049689440.3526708074534160.1081987577639750.2295652173913040.1839
751552795030.433540372670807bash-3.2$
Am I doing something wrong?
C:\> perldoc -f print:
Also be careful not to follow the
print keyword with a left parenthesis
unless you want the corresponding
right parenthesis to terminate the
arguments to the print--interpose a +
or put parentheses around all the
arguments.
Therefore, what you need is:
print( ($val / 8050) . "\n" );
or
print +($val / 8050) . "\n";
The statement you have prints the result of $val / 8050 and then concatenates "\n" to the return value of print and then discards the resulting value.
Incidentally, if you:
use warnings;
then perl will tell you:
print (...) interpreted as function at t.pl line 5.
Useless use of concatenation (.) or string in void context at t.pl line 5.
This is more of a comment than an answer, but I don't know how else to make it and the question is already answered anyway.
Note that using say instead of print neatly sidesteps the whole issue. That is,
#!/usr/bin/perl
use 5.010;
use strict;
use warnings;
my $val = 1;
for ( 1 .. 100 ) {
$val = ($val * $val + 1) % 8051;
say ($val / 8050);
}
works as intended without the issue even coming up. I'm still amazed at how useful say is, given it's such a tiny difference.
It is possible that the line is interpreted as follows
(print($val / 8050)) . " \n";
i.e. the parentheses being used as delimiters for a function argument list, with the ."\n" being silently discarded. Try:
print( ($val/8050) . "\n" );