Perl's rename: GATES, Bill -> Bill Gates - perl

Suppose I have files named like GATES, Bill.jpg and I want to rename them all to Bill Gates.jpg. I can capture the two words
rename 's/^(.*?), (.*?)\./$2 $1\./g' *
To change a case there are some Perl's functions:
$lower = lc("aBcDe"); # $lower is assigned "abcde"
$upper = uc("aBcDe"); # $upper is assigned "ABCDE"
$lower = lcfirst("HELLO"); # $lower is assigned "hELLO"
$upper = ucfirst("hello"); # $upper is assigned "Hello"
I tried to make use of them:
rename 's/^(.*?), (.*?)\./$2 ucfirst($1)\./g' *
But it doesn't work.

You need to add the "e" (eval) flag to the end of the regular expression, otherwise the function won't be executed. This means that the entire second part of the s/// expression has to be a valid Perl expression (instead of a valid string):
rename 's/^(.*?), (.*?)\./"$2 " . ucfirst(lc($1)) . "."/ge' *
(also note the extra space inside the string with $2)
More information on this flag can be found in the perlre documentation.

Should be:
rename 's/^(.*?), (.*?)\./$2 \u\L$1./g' *
Although that doesn’t always work perfectly on Unicode. For those few corner cases it misses, you would want something more like
rename 's/^(\w)(\w*),\s+(\w+)\./$3 \u$1\L$2./g' *
Here’s where you can get a somewhat updated version of the regular rename program.

Related

Append string in the beginning and the end of a line containing certain string

all
I want to know how to append string in the beginning and the end of a line containing certain string using perl?
So for example, my line contains:
%abc %efd;
and I want to append 123 at the beginning of the line and 456 at the end of the line, so it would look like this:
123 %abc %efd 456
8/30/16 UPDATE--------------------------------
So far I have done something like this:
foreach file (find . -type f)
perl -ne 's/^\%abc\s+(\S*)/**\%abc $1/; print;' $file > tmp; mv tmp $file
end
foreach file (find . -type f)
perl -ne 's/$\%def\;\s+(\S*)/\%def\;**\n $1/; print;' $file > tmp; mv tmp $file
end
so this does pretty well except that when abc and def are not in one string.
for example:
%abc
something something something
%def
this would turn out to be
%abc
something something something
%def;
which is not what I want.
Thank you
In you case, you want to append string when line of file match the certain string, it means match and replace.
Firstly, read each line of your input file.
Secondly, check if it match with the string you want to append string into the beginning and the end.
Then replace the match string by the new string which contain additional beginning string, the match string and additional end string.
my $input_file = 'your file name here';
my $search_string = '%abc %efd';
my $add_begin = '123';
my $add_end = '456';
# Read file
open(my $IN, '<', $input_file) or die "cannot open file $input_file";
# Check each line of file
while (my $row = <$IN>) {
chomp $row;
$row =~ s/^($search_string)$/$add_begin $1 $add_end/g;
print $row."\n";
}
Try with input file as below:
%abc %efd
asdahsd
234234
%abc
%efd
%abc%efd
You will receive the result as we expected:
123 %abc %efd 456
asdahsd
234234
%abc
%efd
%abc%efd
Modify the code as your requirement and contact me if there's any issue.
Use m modifier to replacing beginning and ending with line by line.
s/^\%abc/123 $&/mg;
s/\%def$/ 456/mg;
Used together, as /ms, they let the "." match any character whatsoever, while still allowing "^" and "$" to match, respectively, just after and just before newlines within the string. source
Welcome to StackOverflow. We strive to help people solve problems in their existing code and learn languages, rather than simply answer one-off questions, the solutions to which can be easily found in 101 tutorials and documentation. The type of question you've posted doesn't leave a lot of room for learning, and doesn't do much to help future learners. It would help us greatly if you could post a more complete example, including what you've tried so far to get it working.
All that being said, there are two main ways to prepend and append to a string in Perl: 1. the concatenation operator, . and 2. string interpolation.
Concatenation
Use a . to join two strings together. You can chain operations together to compose a longer string.
my $str = '%abc %efd';
$str = '123 ' . $str . ' 456';
say $str; # prints "123 %abc %efd 456" with a trailing newline
Interpolation
Enclose a string in double quotes to instruct Perl to interpolate (i.e. find and evaluate) any Perl-style variables enclosed within the string.
my $str = '%abc %efd';
$str = "123 $str 456";
say $str; # prints "123 %abc %efd 456" with a trailing newline
You'll notice that in both examples we prepended and appended to the existing string. You can also create new variable(s) to hold the result(s) of these operations. Other methods of manipulating and building strings include the printf and sprintf functions, the substr function, the join function, and regular expressions, all of which you will encounter as you continue learning Perl.
As far as looking to see if a string contains a certain substring before performing the operation, you can use the index function or a regular expression:
if (index($str, '%abc %efd') >= 0) {
# or...
if ($str =~ /%abc %efd/) {
Remember to use strict; at the top of your Perl scripts and always (at least while you're learning) declare variables with my. If you're having trouble with the say function, you may need to add the statement use feature 'say'; to the top of your script.
You can find an index of excellent Perl tutorials at learn.perl.org. Good luck and have fun!
UPDATE Here is (I believe) a complete answer to your revised question:
find . -type f -exec perl -i.bak -pe's/^(%abc)\s+(\S*)\s+(%def;)$/**\1 \2 \3**/'
This will modify the files in place and create backup files with the extension .bak. Keep in mind that the expression \S* will only match non-whitespace characters; if you need to match strings that contain whitespace, you will need to update this expression (something like .*? might be workable for you).

index argument contains . perl

If a string contains . representing any character, index doesn't match on it. What to do so that it takes . as any character?
For ex,
index($str, $substr)
if $substr contains . anywhere, index will always return -1
thanks
carol
That is not possible. The documentation says:
The index function searches for one string within another, but without
the wildcard-like behavior of a full regular-expression pattern match.
...
The keywords, you can use for further googlings are:
perl regular expression wildcard
Update:
If you just want to know, if your string matches, using a regular expression could look like that:
my $string = "Hello World!";
if( $string =~ /ll. Worl/ )
{
print "Ahoi! Position: ".($-[0])."\n";
}
This is matching a single character.
$-[0] is the offset into the string of the beginning of the entire
match.
-- http://perldoc.perl.org/perlvar.html
If you want to have a pattern, that is matching an arbitary amount of arbitary characters, you could choose a pattern like...
...
if( $string =~ /ll.*orl/ )
{
...
See perlvar for further information about special perl variables. You will find the variable #LAST_MATCH_START and some explanation about $-[0] over there. There are several more variables, that can help you to find sub matches and to gather other interessting information about your matches...
From perldoc -f index, you can see index() doesn't have any regex syntax:
index STR,SUBSTR
The index function searches for one string within another, but without the wildcard-like behavior of a full regular-
expression pattern match. It returns the position of the first occurrence of SUBSTR in STR at or after POSITION. If
POSITION is omitted, starts searching from the beginning of the string. POSITION before the beginning of the string or after
its end is treated as if it were the beginning or the end, respectively. POSITION and the return value are based at 0 (or
whatever you've set the $[ variable to--but don't do that). If the substring is not found, "index" returns one less than the
base, ordinarily "-1"
A simple test:
$ perl -e 'print index("1234567asdfghj.","j.")'
13
Use regex:
$str =~ /$substr/g;
$index = pos();

Recommended method to refactor variable name in Perl code?

I can use Perltidy to reformat source. Quite useful.
If a source file uses a variable like #m, how can I most easily refactor that into something else, e.g. #miles_travelled?
Using a regular expression to rename does not appear safe, because a separate variable such as $m may also exist (with a different type, in this case a scalar), yet the #m variable can be referenced using an expression like $m[$i].
For example, none of the following will be correct for Perl code:
s/([\$\#])m/$1miles_travelled/g # Will rename scalar with same name
s/\$m/\$miles_travelled/g # Will fail to rename accesses of array
Is there a recommended tool or method for safely renaming a variable name in Perl code?
The variable $m always occurs as $m.
The variable #m always occurs as #m or $m[...].
The variable %m always occurs as %m or $m{...} or #m{...}.
… except with indirect method calls: new $m[...] parses as $m->new([...]). But we can probably ignore this case (use no indirect to make sure).
If we want to cover the first three cases properly, we can
replace a scalar by s/(?<=\$)OLDNAME(?!\s*[\[\{])/NEWNAME/g
replace an array by s/(?<=\#)OLDNAME(?!\{)|(?<=\$)OLDNAME(?=\s*\[)/NEWNAME/g
replace a hash by s/(?<=\%)OLDNAME|(?<=[\$\#])OLDNAME(?=\s*\{)/NEWNAME/g
Note that lookarounds or multiple passes for the different cases are neccessary.
Test:
use Test::More tests => 3;
my $scalar_re = qr/(?<=\$) foo (?!\s*[\[\{])/x;
my $array_re = qr/(?<=\#) foo (?!\{) | (?<=\$) foo (?=\s*\[)/x;
my $hash_re = qr/(?<=\%) foo | (?<=[\$\#]) foo (?=\s*\{)/x;
my $input = '$foo, $foo[1], #foo, $foo{a}, %foo, #foo{qw/a b/}';
my $scalar = '$bar, $foo[1], #foo, $foo{a}, %foo, #foo{qw/a b/}';
my $array = '$foo, $bar[1], #bar, $foo{a}, %foo, #foo{qw/a b/}';
my $hash = '$foo, $foo[1], #foo, $bar{a}, %bar, #bar{qw/a b/}';
is $input =~ s/$scalar_re/bar/xrg, $scalar;
is $input =~ s/$array_re /bar/xrg, $array;
is $input =~ s/$hash_re /bar/xrg, $hash;
The Padre editor will carry out a small number of simple refactorings automatically for you. "Rename variable" is one of them.

Difference between "#" and "$" in Perl

What are the differences between #variable and $variable in Perl?
I have read code with the symbol $ and the symbol # before a variable name.
For example:
$info = "Caine:Michael:Actor:14, Leafy Drive";
#personal = split(/:/, $info);
What are the difference between a variable containing $ as opposed to #?
It isn't really about the variable, but more about the context how the variable is used. If you put a $ in front of the variable name, then it is used in scalar context, if you have a # that means you use the variable in list context.
my #arr; defines variable arr as array
when you want to access one individual element (that is a scalar context), you have to use $arr[0]
You can find more about Perl contexts here: http://www.perlmonks.org/?node_id=738558
All your knowledge about Perl will be crashed with mountains, when you don't feel context of this language.
As many people, you use in your speech single value (scalars) and many things in a set.
So, the difference between all of them:
i have a cat. $myCatName = 'Snowball';
it jump on bed where sit #allFriends = qw(Fred John David);
And you can count them $count = #allFriends;
but can't count them at all cause list of names not countable: $nameNotCount = (Fred John David);
So, after all:
print $myCatName = 'Snowball'; # scalar
print #allFriends = qw(Fred John David); # array! (countable)
print $count = #allFriends; # count of elements (cause array)
print $nameNotCount = qw(Fred John David); # last element of list (uncountable)
So, list is not the same, as an array.
Interesting feature is slices where your mind will play a trick with you:
this code is a magic:
my #allFriends = qw(Fred John David);
$anotherFriendComeToParty =qq(Chris);
$allFriends[#allFriends] = $anotherFriendComeToParty; # normal, add to the end of my friends
say #allFriends;
#allFriends[#allFriends] = $anotherFriendComeToParty; # WHAT?! WAIT?! WHAT HAPPEN?
say #allFriends;
so, after all things:
Perl have an interesting feature about context. your $ and # are sigils, that help Perl know, what you want, not what you really mean.
$ like s, so scalar
# like a, so array
Variables that start $ are scalars, a single value.
$name = "david";
Variables that start # are arrays:
#names = ("dracula", "frankenstein", "dave");
If you refer to a single value from the array, you use the $
print "$names[1]"; // will print frankenstein
From perldoc perlfaq7
What are all these $#%&* punctuation signs, and how do I know when to use them?
They are type specifiers, as detailed in
perldata:
$ for scalar values (number, string or reference)
# for arrays
% for hashes (associative arrays)
& for subroutines (aka functions, procedures, methods)
* for all types of that symbol name. In version 4 you used them like
pointers, but in modern perls you can just use references.
$ is for scalar variables(in your case a string variable.)
# is for arrays.
split function will split the variable passed to it acoording to the delimiter mentioned(:) and put the strings in the array.
Variable name starts with $ symbol called scalar variable.
Variable name starts with # symbol called array.
$var -> can hold single value.
#var -> can hold bunch of values ie., it contains list of scalar values.

Perl Map Function

I'm new to the map and grep functions and I'm trying to make an existing script more concise.
I can "grep" the #tracknames successfully but I'm having a problem with "map". I want #trackartist to return true if two consecutive "--" are found in a line and take the value of $1, otherwise false, but it returns the whole line if the upper condition is not met.
What am I doing wrong?
my #tracknames = grep /^\d\d\..*?(\.(?:flac|wv))$/, <*.*>;
my #trackartist = map { s/^\d\d\.\s(.*?)\s--.*?\.(?:flac|wv)$/$1/; $_; } <*.*>;
Sample of files
01. some track artist 1 -- some track name 1.(flac or wv)
02. some track artist 2 -- some track name 2.(flac or wv)
03. some track artist 3 -- some track name 3.(flac or wv)
etc.
Remember that grep is for filtering a list and map is for transforming a list. Right now, your map statement returns $_ for every item in the list. If $_ matches the pattern in your substitution, it will be modified and replaced with the first match. Otherwise, it's not modified and the original $_ is returned.
It sounds like you want to filter out items that don't match the pattern. One way would be to combine a map and a grep:
my #trackartist = map { s/^\d\d\.\s(.*?)\s--.*?\.(?:flac|wv)$/$1/; $_; }
grep { /^\d\d\.\s(.*?)\s--.*?\.(?:flac|wv)$/ } <*.*>;
Of course, this means you're doing the same pattern match twice. Another approach is to do a transform with map, but transform anything that doesn't match the pattern into an empty list.
my #trackartist = map { /^\d\d\.\s(.*?)\s--.*?\.(?:flac|wv)$/ ? $1 : ( ) } <*.*>
This uses the ternary conditional operator (?:) to check if the regex matches (returning a true value). If it does, $1 is returned from the map block, if not, an empty list ( ) is returned, which adds nothing to the list resulting from the map.
As a side note, you might want to look into using the glob function rather than <>, which has some disadvantages.
I like map and grep as much as the next guy, but your task seems more suited to a divide-and-conquer parsing approach. I say this because your comments suggest that your interest in map is leading you down a road where you'll end up with a data model consisting of parallel arrays -- #tracks, #artists, etc. -- which is often difficult to maintain in the long run. Here's a sketch of what I mean:
my #tracks;
while (my $file_name = <DATA>){ # You'll use glob() or <*.*>
# Filter out unwanted files.
my ($num, $artist_title, $ext) = $file_name =~ /
^ (\d\d) \. \s*
(.*)
\. (flac|wv) $
/x;
next unless $ext;
# Try to parse the artist and title. Adjust as needed.
my ($artist, $title) = split /\s+--\s+/, $artist_title, 2;
($artist, $title) = ('UNKNOWN', $artist) unless $title;
# Store all info as a hash ref. No need for parallel arrays.
push #tracks, {
file_name => $file_name,
ext => $ext,
artist => $artist,
title => $title,
};
}
__DATA__
01. Perl Jam -- Open or die.wv
02. Perl Jam -- Map to nowhere.flac
03. Perl Jam -- What the #$#!?.wv
04. Perl Jam -- Regex blues.wv
05. Perl Jam -- Use my package, baby.wv
06. Perl Jam -- No warnings.wv
07. Perl Jam -- Laziness ISA virtue.wv
08. Guido and the Pythons -- Home on the xrange.flac
09. Guido and the Pythons -- You gotta keep em generated.flac
10. StackOverflow medley.wv
foo.txt