Perl wildcard match of filenames from "ls" output - regex

#!/usr/bin/perl
my #allFiles=`ls *.gz`;
for my $file (#allFiles) {
if ($file =~ '0000*.gz') {
print $file;
}
}
I am trying this above code to print all filenames that have a prefix of 0000. Like 00001.gz, 00002.gz etc

A close equivalent to the shell's wildcard * in regex is .*. The * quantifier means that the pattern before it matches "zero or more" times and . means "any character," see Regular Expressions in perlre. But as it seems that you want something after the zeros then use .+ instead, to match any character one-or-more times. To match a literal period escape it, \.
Next, there is no reason to use an external command for what you do. In Perl
my #allFiles = glob "*.gz";
The documentation is linked at the end.
Finally, please always enable warnings and strict.
Altogether
use warnings;
use strict;
my #allFiles = glob "*.gz";
foreach my $file (#allFiles) {
if ($file =~ /^0000.+\.gz/) {
print "$file\n";
}
}
The regex pattern matches: 0000 at the beginning of the string (^), followed by any character (.) matched one-or-more times (+), then a literal period (\.) and literal gz. Note that .+ means that the . matches one-or-more times, it need not be the same character.
Adjust to what best suits your actual need and the directory content. For example, if you want files with only digits following zeros, you need /^0000\d+\.gz/. To catch a file 00001a.gz as well you need to allow for non-digits after a string of digits, for instance by /^0000\d+.*\.gz/.
See perlretut for a regex tutorial and glob, or better File::Glob for things with spaces in names.
There are other ways to do this. For example, you need to filter a list of file names, so
my #files = grep { /^0000.+\.gz/ } glob "*.gz";
The glob is in the list context imposed by grep so it returns the list of all files that it matches. (In the scalar context it iterates through them.) The code in grep's block runs for each and if it evaluates to true that element passes. It is the same regex, applied by default to $_ variable that is the implicit iterator (and aliased to the currently processed element). So grep
returns the desired list.
For your specific example even just this will do
my #files = glob "0000[0-9].gz";
print "$_\n" for #files;
This fetches all files with a single digit following 0000, then .gz.
See the list of accepted meta characters in the linked File::Glob docs.

Related

How to reconstruct regex matched part

I have simplify some latex math formula within text, for example
This is ${\text{BaFe}}_{2}{\text{As}}_{2}$ crystal
I want to transform this into
This is BaFe2As2 crystal
That is to concatenate only content within inner most bracket.
I figure out that I can use regex pattern
\{[^\{\}]*\}
to match those inner most bracket. But the problem is how to concatenate them together?
I don't know if this could be done in notepad++ regex replacement. If notepad++ is not capable, I can also accept perl one-liner solution.
There may clearly be multiple such equations (the markup between two $s) in the document. So while you need to assemble text between all {}, this also need be constrained within a $ pair. Then all such equations need be processed.
Matching that in a single pattern results in a complex regex. Instead, we can first extract everything within a pair of $s and then gather text within {}s from that, simplifying the regex a lot. This makes two passes over each equation but a Latex document is small for computational purposes and the loss of efficiency can't be noticed.
use warnings;
use strict;
use feature 'say';
my $text = q(This is ${\text{BaFe}}_{2}{\text{As}}_{2}$ crystal,)
. q( and ${\text{Some}}{\mathbf{More}}$ text);
my #results;
while ($text =~ /\$(.*?)\$/g) {
my $eq = $1;
push #results, join('', $eq =~ /\{([^{}]+)\}/g);
}
say for #results;
This prints lines BaFe2As2 and SomeMore.
The regex in the while condition captures all chars between two $s. After the body of the loop executes and the condition is checked again, the regex continues searching the string from the position of the previous match. This is due to the "global" modifier /g in scalar context, imposed on regex since it is in the loop condition. Once there are no more matches the loop terminates.
In the body we match between {}, and again due to /g this is done for all {}s in the equation. Here, however, the regex is in the list context (as it is assigned to an array) and then /g makes it return all matches. They are joined into a string, which is added to the array.
In order to replace the processed equation, use this in a substitution instead
$text =~ s{ \$(.*?)\$ }{ join('', $1 =~ /\{([^{}]+)\}/g) }egx;
where the modifier e makes it so that the replacement part is evaluated as Perl code, and the result of that used to replace the matched part. Then in it we can run our regex to match content of all {} and join it into the string, as explained above. I use s{}{} delimiters, and x modifier so to be able to space things in the matching part as well.
Since the whole substitution has the g modifier the regex keeps going through $text, as long as there are equations to match, replacing them with what's evaluated in the replacement part.
I use a hard-coded string (extended) from the question, for an easy demo. In reality you'd read a file into a scalar variable ("slurp" it) and process that.
This relies on the question's premise that text of interest in an equation is cleanly between {}.
Missed the part that a one-liner is sought
perl -0777 -wnE'say join("", $1=~/\{([^{}]+)\}/g) while /\$(.*?)\$/g' file.tex
With -0777 the file is read whole ("slurped"), and as -n provides a loop over input lines it is in the $_ variable; the regex in the while condition works by default on $_. In each interation of while the contents of the captured equation, in $1, is directly matched for {}s.
Then to replace each equation and print out the whole processed file
perl -0777 -wne's{\$(.*?)\$}{join "", $1=~/\{([^{}]+)\}/g}eg; print' file.tex
where I've removed extra spaces and (unnecessary) parens on join.
Use this regex in Notepad++. I have tried to match everything which is NOT present between the innermost curly brackets and then replaced the match with a blank string.
[^{}]*\{|\}[^{}]*
Click for Demo
Explanation:
[^{}]*\{ - matches 0+ occurrences of any character that is neither { nor } followed by {
| - OR
\}[^{}]* - matches } followed by 0+ occurrences of any character that is neither { nor }
Before Replacement:
After Replacement:
UPDATE:
Try this updated regex:
\$?(?=[^$]*\$[^$]*$)(?:[^{}]*{|}[^{}]*)(?=[^$]*\$[^$]*$)\$?
Click for Demo

Regex to find(/replace) multiple instances of character in string

I have a (probably very basic) question about how to construct a (perl) regex, perl -pe 's///g;', that would find/replace multiple instances of a given character/set of characters in a specified string. Initially, I thought the g "global" flag would do this, but I'm clearly misunderstanding something very central here. :/
For example, I want to eliminate any non-alphanumeric characters in a specific string (within a larger text corpus). Just by way of example, the string is identified by starting with [ followed by #, possibly with some characters in between.
[abc#def"ghi"jkl'123]
The following regex
s/(\[[^\[\]]*?#[^\[\]]*?)[^a-zA-Z0-9]+?([^\[\]]*?)/$1$2/g;
will find the first " and if I run it three times I have all three.
Similarly, what if I want to replace the non-alphanumeric characters with something else, let's say an X.
s/(\[[^\[\]]*?#[^\[\]]*?)[^a-zA-Z0-9]+?([^\[\]]*?)/$1X$2/g;
does the trick for one instance. But how can I find all of them in one go?
The reason your code doesn't work is that /g doesn't rescan the string after a substitution. It finds all non-overlapping matches of the given regex and then substitutes the replacement part in.
In [abc#def"ghi"jkl'123], there is only a single match (which is the [abc#def" part of the string, with $1 = '[abc#def' and $2 = ''), so only the first " is removed.
After the first match, Perl scans the remaining string (ghi"jkl'123]) for another match, but it doesn't find another [ (or #).
I think the most straightforward solution is to use a nested search/replace operation. The outer match identifies the string within which to substitute, and the inner match does the actual replacement.
In code:
s{ \[ [^\[\]\#]* \# \K ([^\[\]]*) (?= \] ) }{ $1 =~ tr/a-zA-Z0-9//cdr }xe;
Or to replace each match by X:
s{ \[ [^\[\]\#]* \# \K ([^\[\]]*) (?= \] ) }{ $1 =~ tr/a-zA-Z0-9/X/cr }xe;
We match a prefix of [, followed by 0 or more characters that are not [ or ] or #, followed by #.
\K is used to mark the virtual beginning of the match (i.e. everything matched so far is not included in the matched string, which simplifies the substitution).
We match and capture 0 or more characters that are not [ or ].
Finally we match a suffix of ] in a look-ahead (so it's not part of the matched string either).
The replacement part is executed as a piece of code, not a string (as indicated by the /e flag). Here we could have used $1 =~ s/[^a-zA-Z0-9]//gr or $1 =~ s/[^a-zA-Z0-9]/X/gr, respectively, but since each inner match is just a single character, it's also possible to use a transliteration.
We return the modified string (as indicated by the /r flag) and use it as the replacement in the outer s operation.
So...I'm going to suggest a marvelously computationally inefficient approach to this. Marvelously inefficient, but possibly still faster than a variable-length lookbehind would be...and also easy (for you):
The \K causes everything before it to be dropped....so only the character after it is actually replaced.
perl -pe 'while (s/\[[^]]*#[^]]*\K[^]a-zA-Z0-9]//){}' file
Basically we just have an empty loop that executes until the search and replace replaces nothing.
Slightly improved version:
perl -pe 'while (s/\[[^]]*?#[^]]*?\K[^]a-zA-Z0-9](?=[^]]*?])//){}' file
The (?=) verifies that its content exists after the match without being part of the match. This is a variable-length lookahead (what we're missing going the other direction). I also made the *s lazy with the ? so we get the shortest match possible.
Here is another approach. Capture precisely the substring that needs work, and in the replacement part run a regex on it that cleans it of non-alphanumeric characters
use warnings;
use strict;
use feature 'say';
my $var = q(ah [abc#def"ghi"jkl'123] oh); #'
say $var;
$var =~ s{ \[ [^\[\]]*? \#\K ([^\]]+) }{
(my $v = $1) =~ s{[^0-9a-zA-Z]}{}g;
$v
}ex;
say $var;
where the lone $v is needed so to return that and not the number of matches, what s/ operator itself returns. This can be improved by using the /r modifier, which returns the changed string and doesn't change the original (so it doesn't attempt to change $1, what isn't allowed)
$var =~ s{ \[ [^\[\]]*? \#\K ([^\]]+) }{
$1 =~ s/[^0-9a-zA-Z]//gr;
}ex;
The \K is there so that all matches before it are "dropped" -- they are not consumed so we don't need to capture them in order to put them back. The /e modifier makes the replacement part be evaluated as code.
The code in the question doesn't work because everything matched is consumed, and (under /g) the search continues from the position after the last match, attempting to find that whole pattern again further down the string. That fails and only that first occurrence is replaced.
The problem with matches that we want to leave in the string can often be remedied by \K (used in all current answers), which makes it so that all matches before it are not consumed.

How to escape a string that looks like a regular expression in Perl

I have a script that, among other things, searches a list of text files to replace a Windows path (text string) with another path.
The problem is that some of the folder names begin with a number and a dash. Perl seems to think that I am trying to invoke a regular expression here. I get the message, "Reference to nonexistent group in regex".
the string looks like this:
\\\BAGlobal\6-Engineering\3-Tech
I have quoted it like this:
my $find = "\\\\\\\BAGlobal\\\6-Engineering\\\3-Tech"
How do I escape the 6- and 3- ?
The problem is not the the dash in 6- but all the backslashes \.
It thinks that \3 and \6 are back-references to previously matched groups, like /foo(bar) foo\1/ would match the string foobar foobar.
If you use this in a pattern match you need to either include \Q and \E to add quoting, or apply the quotemeta built-in to your $find.
my $find = '\\\\\\\BAGlobal\\\6-Engineering\\\3-Tech';
$string =~ m/\Q$find\E/;
Or with quotemeta.
my $find = quotemeta '\\\\\\\BAGlobal\\\6-Engineering\\\3-Tech';
$string =~ m/$find/;
Also see perlre.
Note that your example code is probably wrong. The number of backslashes you have there is uneven, and double quotes "" interpolate, so each pair of backslashes \\ turn into one actual backslash in the string. But because you have 7 of them, the last one is seen as the escape for B, turning that into \B, which is not a valid escape sequence. I used single quotes '' in my code above.

Replace specific capture group instead of entire regex in Perl

I've got a regular expression with capture groups that matches what I want in a broader context. I then take capture group $1 and use it for my needs. That's easy.
But how to use capture groups with s/// when I just want to replace the content of $1, not the entire regex, with my replacement?
For instance, if I do:
$str =~ s/prefix (something) suffix/42/
prefix and suffix are removed. Instead, I would like something to be replaced by 42, while keeping prefix and suffix intact.
As I understand, you can use look-ahead or look-behind that don't consume characters. Or save data in groups and only remove what you are looking for. Examples:
With look-ahead:
s/your_text(?=ahead_text)//;
Grouping data:
s/(your_text)(ahead_text)/$2/;
If you only need to replace one capture then using #LAST_MATCH_START and #LAST_MATCH_END (with use English; see perldoc perlvar) together with substr might be a viable choice:
use English qw(-no_match_vars);
$your_string =~ m/aaa (bbb) ccc/;
substr $your_string, $LAST_MATCH_START[1], $LAST_MATCH_END[1] - $LAST_MATCH_START[1], "new content";
# replaces "bbb" with "new content"
This is an old question but I found the below easier for replacing lines that start with >something to >something_else. Good for changing the headers for fasta sequences
while ($filelines=~ />(.*)\s/g){
unless ($1 =~ /else/i){
$filelines =~ s/($1)/$1\_else/;
}
}
I use something like this:
s/(?<=prefix)(group)(?=suffix)/$1 =~ s|text|rep|gr/e;
Example:
In the following text I want to normalize the whitespace but only after ::=:
some text := a b c d e ;
Which can be achieved with:
s/(?<=::=)(.*)/$1 =~ s|\s+| |gr/e
Results with:
some text := a b c d e ;
Explanation:
(?<=::=): Look-behind assertion to match ::=
(.*): Everything after ::=
$1 =~ s|\s+| |gr: With the captured group normalize whitespace. Note the r modifier which makes sure not to attempt to modify $1 which is read-only. Use a different sub delimiter (|) to not terminate the replacement expression.
/e: Treat the replacement text as a perl expression.
Use lookaround assertions. Quoting the documentation:
Lookaround assertions are zero-width patterns which match a specific pattern without including it in $&. Positive assertions match when their subpattern matches, negative assertions match when their subpattern fails. Lookbehind matches text up to the current match position, lookahead matches text following the current match position.
If the beginning of the string has a fixed length, you can thus do:
s/(?<=prefix)(your capture)(?=suffix)/$1/
However, ?<= does not work for variable length patterns (starting from Perl 5.30, it accepts variable length patterns whose length is smaller than 255 characters, which enables the use of |, but still prevents the use of *). The work-around is to use \K instead of (?<=):
s/.*prefix\K(your capture)(?=suffix)/$1/

How to Use Regular Expression to Look for the something NOT of Certain pattern

Using Perl style regexp, is it possible to look for something not of certain pattern?
For example, [^abc] looks for a SINGLE CHARACTER not a nor b nor c.
But can I specify something longer than a single character?
For example, in the following string, I want to search for the first word which is not a top level domain name and contains no uppercase letter, or perhaps some more complicated rules like having 3-10 characters. In my example, this should be "abcd":
net com org edu ABCE abcdefghijklmnoparacbasd abcd
You can do it using negative look-ahead assertions as:
^(?!(?:net|com|org|edu)$)(?!.*[A-Z])[a-z]{3,10}$
See it
Explanation:
^ - Start anchor
$ - End anchor
(?:net|com|org|edu) - Alternation, matches net or com or org or edu
(?!regex) - Negative lookahead.
Matches only if the string does not match the regex.
So the part (?!(?:net|com|org|edu)$) ensures that input is not one of the top level domains.
The part (?!.*[A-Z]) ensures that the input does not have a upper case letter.
The part [a-z]{3,10}$ ensures that the input is of length atleast 3 and atmost 10.
Just use the "not match" operator: !~
So just create your expression and then see that a variable does not match it:
if ($var !~ /abc/) {
...
}
IMHO its easier to do some matching with regexp and some checks with perl.
#!/usr/bin/env perl
use strict;
use warnings;
my $s = "net com org edu ABCE abcdefghijklmnoparacbasd abcd";
# loop short words (a-z might not be what you want though)
foreach( $s =~ /(\b[a-z]{3,10}\b)/g ){
print $_, "\n" if is_tpl($_);
}
BTW, there are a lot of top level domains ..