Perl regular expression to modify a filename - regex

I am trying to remove the Ist part and last part of the file name.
I have a filename like /external/prop/new/test/File.Name.will.BE.this.extension.date
I want to remove the first part of the directory (/external) and the last part of the filename extension (.date) so my output file name would be /prop/new/test/File.Name.will.BE.this.extension
eg:
OLD FILE name: /external/prop/new/test/FACL.Prop.for.BBG.txt.09242012
NEW FILENAME: /prop/new/test/FACL.Prop.for.BBG.txt
OLD FILE name: /external/prop/old/test/set2/FACL.Prop.FITCH.csv.09242012
NEW FILENAME: /prop/old/test/set2/FACL.Prop.FITCH.csv
I had tried something like
my($FinalName, $Finalextension) = split /\.(?!.*\.)/, substr($Fname,$Flength);
but it is not quite helpful.
/external will always remain the same but the date will always vary and I can't just remove the numbers as the .extension can be numbers.

$Fname =~ s|^/external||; # Remove leading "external" from path
$Fname =~ s|\.\d{8}$||; # Remove date from end of filename

my $qfn = '/external/prop/new/test/FACL.Prop.for.BBG.txt.09242012';
$qfn =~ s{\.[^.]*\z}{}; # Remove last "extension".
$qfn =~ s{^/[^/]+}{}; # Remove first part of path.

Try this regex which captures the text you need in $1:
^\/[^/]+(.*?)\.\d{8}$
This assumes your date is always 8 numbers. You can replace the last \d{8} with your appropriate date regex.
This can be tested in RegExr here.
Regex Break-up:
^\/ matches the beginning of the line followed by forward slash
(escaped)
[^/]+ matches all text until it finds the next forward slash (to
mark the end of /external)
(.*?) matches AND captures non-greedily all text you need until it finds the last of the pattern
\.\d{8}$ matches the last period followed by 8 digit date followed by end of line

Related

Perl Regex to exclude certain strings within the middle of a string

I have this string:
The file FILENAME has not been received
I'm trying to get the regex to match this, but not if the string is this (for example):
The file FAILNAME has not been received
I've got this regex so far:
/^(?=.*?\bThe\sfile\b)((?!FAILNAME).)*$/
But I'm unsure how to continue the expected text after the exclusion.
I hope I've explained that correctly :)
Thanks in advance!
I'd do it in two steps:
if ($message =~ /\AThe file (\S+) has not been received\z/ && $1 ne 'FAILNAME') {
I.e. use a regex to validate the general format and extract the filename, then check the extracted name separately.
Why cram everything into a single regex?
Speaking of which, you can actually cram arbitrary conditions into a regex. I wouldn't recommend it in this case, but:
/\AThe file (\S+) has not been received\z(?(?{ $1 eq 'FAILNAME' })(*FAIL))/
This extended pattern essentially says "if $1 equals FAILNAME, fail the match".
You could move the negative lookahead to after file followed by a whitespace character to assert what is directly on the right is not FAILNAME:
^The\sfile\s(?!\bFAILNAME\b).*$
Or of it can not occur in the string after The file use a quantifier:
^The\sfile\s(?!.*\bFAILNAME\b).*$
If there can not be anything before and after FAILNAME you could lookarounds:
^The\sfile\s(?!.*(?<!\S)FAILNAME(?!\S)).*$
Regex demo
My guess is that we wish to fail those that have FAILNAME, which your original expression seems to be working fine, and we'd then slightly modify that, which this might work:
^(?=The\sfile\s)(?!.*\s\bFAILNAME\b\s.*).*$
Demo 1
Here we are adding two spaces as an extra left and right boundaries, which if we do not wish to have those, we'd be simply excluding.
Example
use strict;
my $str = 'The file FILENAME has not been received
The file FILENAME has been received
The file FILENAME
The file AFAILNAME has not been received
FILENAME
The file FAILNAME has not been received
FAILNAME has not been received
The file FAILNAME
';
my $regex = qr/^(?=The\sfile\s)(?!.*\s\bFAILNAME\b\s.*).*$/mp;
if ( $str =~ /$regex/g ) {
print "Whole match is ${^MATCH} and its start/end positions can be obtained via \$-[0] and \$+[0]\n";
# print "Capture Group 1 is $1 and its start/end positions can be obtained via \$-[1] and \$+[1]\n";
# print "Capture Group 2 is $2 ... and so on\n";
}
# ${^POSTMATCH} and ${^PREMATCH} are also available with the use of '/p'
# Named capture groups can be called via $+{name}
RegEx Circuit
jex.im visualizes regular expressions:

Perl regex matching alternative file names

Example:
I used to use regex to get extension from the file name:
my $name = "file.zip";
my ($fname, $fext) = $name =~ /(.*)\.(.*)/;
# file
# zip
Now, I need to make sure that it also properly catches .tar.gz files, in case name includes it, otherwise falls back to example above. I did the following:
my $name = "file.tar.gz";
my ($fname, $fext) = $name =~ /(.*)\.(tar\.gz$)|(.*)\.(.*)/;
# file
# tar.gz
Problem:
The problem is that now it only works for file.tar.gz and doesn't fall back to catching regular files, like file.zip, and returns empty, in the second case.
How do I do this in one regex, so it successfully works for file.tar.gz and file.zip. What do I miss?
You may use
/^(.*?)\.(tar\.gz|[^.]*)$/
Details
^ - start of a line
(.*?) - Group 1: any 0+ chars other than line break chars, as few as possible
\. - a dot
(tar\.gz|[^.]*) - Group 2: tar.gz or any 0+ chars other than a dot
$ - end of line.
See the regex demo.
Alternatively, you might also use your original pattern but wrap it with a branch reset group:
/(?|(.*)\.(tar\.gz)|(.*)\.(.*))$/
See this regex demo. It will assign the same IDs to the corresponding capturing groups inside the branch reset group. Since (.*)\.(tar\.gz) will be tried first, if there is a string ending with .tar.gz, the first alternation part ((.*)\.(tar\.gz)) will match, else, the second one ((.*)\.(.*)) will consume the string.
perl -e '$name= "file.zip";($fname,$fext)=$name =~ /(.*)\.(tar\.gz|zip)$/ ;print "$fname.$fext"'
file.zip
the number of captured group, ie. 4, is greater than that of assigned variable ($fname,$fext)= , ie. 2
only the first 2 group is assigned

sed only replacing last occurrence of match - need to match all

I would like to replace all { } on a certain line with [ ], but unfortunately I am only able to match the last occurrence of the regexp.
I have a config file which has structure as follows:
entry {
id 123456789
desc This is a description of {foo} and was added by {bar}
trigger 987654321
}
I have the following sed, of which is able to replace the last match 'bar' but not 'foo':
sed s'/\(desc.*\){\(.*\)}/\1\[\2\]/g' < filename
I anchor this search to the line containing 'desc' as I would hate for it to replace the delimiting braces of each 'entry' block.
For the life of me I am unable to figure out how to replace all of the occurrences.
Any help is appreciated - have been learning all day and unable to read any more tutorials for fear that my corneas might crack.
Thanks!
Try the following:
sed '/desc/ s/{\([^}]*\)}/[\1]/g' filename
The search and replace in the above command will only be done for lines that match the regex /desc/, however I don't think this is actually necessary because sed processes text a line at a time, so even without this you wouldn't be replacing braces on the 'entry' block. This means that you could probably simplify this to the following:
sed 's/{\([^}]*\)}/[\1]/g' filename
Instead of .* inside of the capturing group [^}]* is used which will match everything except closing braces, that way you won't match from the first opening to the last closing.
Also, you can just provide the file name as the final argument to sed instead of using input redirection.

Remove characters and numbers from a string in perl

I'm trying to rename a bunch of files in my directory and I'm stuck at the regex part of it.
I want to remove certain characters from a filename which appear at the beginning.
Example1: _00-author--book_revision_
Expected: Author - Book (Revision)
So far, I am able to use regex to remove underscores & captialize the first letter
$newfile =~ s/_/ /g;
$newfile =~ s/^[0-9]//g;
$newfile =~ s/^[0-9]//g;
$newfile =~ s/^-//g;
$newfile = ucfirst($newfile);
This is not a good method. I need help in removing all characters until you hit the first letter, and when you hit the first '-' I want to add a space before and after '-'.
Also when I hit the second '-' I want to replace it with '('.
Any guidance, tips or even suggestions on taking the right approach is much appreciated.
So do you want to capitalize all the components of the new filename, or just the first one? Your question is inconsistent on that point.
Note that if you are on Linux, you probably have the rename command, which will take a perl expression and use it to rename files for you, something like this:
rename 'my ($a,$b,$r);$_ = "$a - $b ($r)"
if ($a, $b, $r) = map { ucfirst $_ } /^_\d+-(.*?)--(.*?)_(.*?)_$/' _*
Your instructions and your example don't match.
According to your instructions,
s/^[^\pL]+//; # Remove everything until first letter.
s/-/ - /; # Replace first "-" with " - "
s/-[^-]*\K-/(/; # Replace second "-" with "("
According to your example,
s/^[^\pL]+//;
s/--/ - /;
s/_/ (/;
s/_/)/;
s/(?<!\pL)(\pL)/\U$1/g;
$filename =~ s,^_\d+-(.*?)--(.*?)_(.*?)_$,\u\1 - \u\2 (\u\3),;
My Perl interpreter (using strict and warnings) says that this is better written as:
$filename =~ s,^_\d+-(.*?)--(.*?)_(.*?)_$,\u$1 - \u$2 (\u$3),;
The first one probably is more sedish for its taste! (Of course both version works just the same.)
Explanation (as requested by stema):
$filename =~ s/
^ # matches the start of the line
_\d+- # matches an underscore, one or more digits and a hypen minus
(.*?)-- # matches (non-greedyly) anything before two consecutive hypen-minus
# and captures the entire match (as the first capture group)
(.*?)_ # matches (non-greedyly) anything before a single underscore and
# captures the entire match (as the second capture group)
(.*?)_ # does the same as the one before (but captures the match as the
# third capture group obviously)
$ # matches the end of the line
/\u$1 - \u$2 (\u$3)/x;
The \u${1..3} in replacement specification simply tells Perl to insert the capture groups from 1 to 3 with their first character made upper-case. If you'd wanted to make the entire match (in a captured group) upper-case you'd had to use \U instead.
The x flags turns on verbose mode, which tells the Perl interpreter that we want to use # comments, so it will ignore these (and any white space in the regular expression - so if you want to match a space you have to use either \s or \). Unfortunately I couldn't figure out how to tell Perl to ignore white space in the * replacement* specification - this is why I've written that on a single line.
(Also note that I've changed my s terminator from , to / - Perl barked at me if I used the , with verbose mode turned on ... not exactly sure why.)
If they all follow that format then try:
my ($author, $book, $revision) = $newfiles =~ /-(.*?)--(.*?)_(.*?)_/;
print ucfirst($author ) . " - $book ($revision)\n";

Regex for extracting filename from path

I need to extract just the filename (no file extension) from the following path....
\\my-local-server\path\to\this_file may_contain-any&character.pdf
I've tried several things, most based off of something like http://regexr.com?302m5 but can't quite get there
^\\(.+\\)*(.+)\.(.+)$
This regex has been tested on these two examples:
\var\www\www.example.com\index.php
\index.php
First block "(.+\)*" matches directory path.
Second block "(.+)" matches file name without extension.
Third block "(.+)$" matches extension.
This will get the filename but will also get the dot. You might want to truncate the last digit from it in your code.
[\w-]+\.
Update
#Geoman if you have spaces in file name then use the modified pattern below
[ \w-]+\. (space added in brackets)
Demo
This is just a slight variation on #hmd's so you don't have to truncate the .
[ \w-]+?(?=\.)
Demo
Really, thanks goes to #hmd. I've only slightly improved on it.
Try this:
[^\\]+(?=\.pdf$)
It matches everything except back-slash followed by .pdf at the end of the string.
You can also (and maybe it's even better) take the part you want into the capturing group like that:
([^\\]+)\.pdf$
But how you refer to this group (the part in parenthesis) depends on the language or regexp flavor you're using. In most cases it'll be smth like $1, or \1, or the library will provide some method for getting capturing group by its number after regexp match.
I use #"[^\\]+$"
That gives the filename including the extension.
I'm using this regex to replace the filename of the file with index. It matches a contiguous string of characters that doesn't contain a slash and is followed by a . and a string of word characters at the end of the string. It will retrieve the filename including spaces and dots but will ignore the full file extension.
const regex = /[^\\/]+?(?=\.\w+$)/
console.log('/path/to/file.png'.match(regex))
console.log('/path/to/video.webm'.match(regex))
console.log('/path/to/weird.file.gif'.match(regex))
console.log('/path with/spaces/and file.with.spaces'.match(regex))
If anyone is looking for a windows absolute path (and relative path) javascript regular expression in javascript for files:
var path = "c:\\my-long\\path_directory\\file.html";
((/(\w?\:?\\?[\w\-_\\]*\\+)([\w-_]+)(\.[\w-_]+)/gi).exec(path);
Output is:
[
"c:\my-long\path_directory\file.html",
"c:\my-long\path_directory\",
"file",
".html"
]
Here's a slight modification to Angelo's excellent answer that allows for spaces in the path, filename and extension as well as missing parts:
function parsePath (path) {
var parts = (/(\w?\:?\\?[\w\-_ \\]*\\+)?([\w-_ ]+)?(\.[\w-_ ]+)?/gi).exec(path);
return {
path: parts[0] || "",
folder: parts[1] || "",
name: parts[2] || "",
extension: parts[3] || "",
};
}
If you want to return the file name with its extension, Regex should be as below:
[A-Za-z0-9_\-\.]+\.[A-Za-z0-9]+$
works for
path/to/your/filename.some
path/to/your/filename.some.other
path\to\your\filename.some
path\to\your\filename.some.other
http://path/to/your/filename.some
http://path/to/your/filename.some.other
And so on
Which returns full file name with extension(eg: filename.some or filename.some.other)
If you want to return file name without the last extension Regex should be as below:
[A-Za-z0-9_\-\.]+(?=\.[A-Za-z0-9]+$)
Which returns full file name without last extension(eg: "filename" for "filename.some" and "filename.some" for "filename.some.other")
Click the Explain button on these links shown TEST to see how they work.
This is specific to the pdf extension.
TEST ^.+\\([^.]+)\.pdf$
This is specific to any extension, not just pdf.
TEST ^.+\\([^.]+)\.[^\.]+$
([^.]+)
This is the $1 capture group to extract the filename without the extension.
\\my-local-server\path\to\this_file may_contain-any&character.pdf
will return
this_file may_contain-any&character
TEST ^(.*[\\\/])?(.*?)(\.[^.]*?|)$
example:
/^(.*[\\\/])?(.*?)(\.[^.]*?|)$/.exec("C:\\folder1\\folder2\\foo.ext1.ext")
result:
0: "C:\folder1\folder2\foo.ext1.ext"
1: "C:\folder1\folder2\"
2: "foo.ext1"
3: ".ext"
the $1 capture group is the folder
the $2 capture group is the name without extension
the $3 capture group is the extension (only the last)
works for:
C:\folder1\folder2\foo.ext
C:\folder1\folder2\foo.ext1.ext
C:\folder1\folder2\name-without extension
only name
name.ext
C:\folder1\folder2\foo.ext
/folder1/folder2/foo.ext
C:\folder1\folder2\foo
C:\folder1\folder2\
C:\special&chars\folder2\f [oo].ext1.e-x-t
Answer with:
File name and directory space support
Named capture group
Gets unlimited file extensions (captures file.tar.gz, not just file.tar)
*NIX and Win support
^.+(\\|\/)(?<file_name>([^\\\/\n]+)(\.)?[^\n\.]+)$
Explanation:
^.+(\\|\/) Gets anything up to the final / or \ in a file path
(?<file_name> Begin named capture group
([^\\\/\n]+) get anything except for a newline or new file
(\.)?[^\n\.]+ Not really needed but it works well for issues with odd characters in file names
)$ End named capture group and end line
Note that if you're putting this in a string and you need to escape backslashes (such as with C) you'll be using this string:
"^.+(\\\\|\/)(?<file_name>([^\\\/\n]+)(\.)?[^\n\.]+)$"
Here is an alternative that works on windows/unix:
"^(([A-Z]:)?[\.]?[\\{1,2}/]?.*[\\{1,2}/])*(.+)\.(.+)"
First block: path
Second block: dummy
Third block: file name
Fourth block: extension
Tested on:
".\var\www\www.example.com\index.php"
"\var\www\www.example.com\index.php"
"/var/www/www.example.com/index.php"
"./var/www/www.example.com/index.php"
"C:/var/www/www.example.com/index.php"
"D:/var/www/www.example.com/index.php"
"D:\\var\\www\\www.example.com\\index.php"
"\index.php"
"./index.php"
This regular expression extract the file extension, if group 3 isn't null it's the extension.
.*\\(.*\.(.+)|.*$)
also one more for file in dir and root
^(.*\\)?(.*)(\..*)$
for file in dir
Full match 0-17 `\path\to\file.ext`
Group 1. 0-9 `\path\to\`
Group 2. 9-13 `file`
Group 3. 13-17 `.ext`
for file in root
Full match 0-8 `file.ext`
Group 2. 0-4 `file`
Group 3. 4-8 `.ext`
For most of the cases ( that is some win , unx path , separator , bare file name , dot , file extension ) the following one is enough:
// grap the dir part (1), the dir sep(2) , the bare file name (3)
path.replaceAll("""^(.*)[\\|\/](.*)([.]{1}.*)""","$3")
Direct approach:
To answer your question as it's written, this will provide the most exact match:
^\\\\my-local-server\\path\\to\\(.+)\.pdf$
General approach:
This regex is short and simple, matches any filename in any folder (with or without extension) on both windows and *NIX:
.*[\\/]([^.]+)
If a file has multiple dots in its name, the above regex will capture the filename up to the first dot. This can easily be modified to match until the last dot if you know that you will not have files without extensions or that you will not have a path with dots in it.
If you know that the folder will only contain .pdf files or you are only interested in .pdf files and also know that the extension will never be misspelled, I would use this regex:
.*[\\/](.+)\.pdf$
Explanation:
. matches anything except line terminators.
* repeats the previous match from zero to as many times as possible.
[\\/] matches a the last backslash or forward slash (previous ones are consumed by .*). It is possible to omit either the backslash or the forward slash if you know that only one type of environment will be used.
If you want to capture the path, surround .* or .*[\\/] in parenthesis.
Parenthesis will capture what is matched inside them.
[^.] matches anything that is not a literal dot.
+ repeats the previous match one or more times, as many as possible.
\. matches a literal dot.
pdf matches the string pdf.
$ asserts the end of the string.
If you want to match files with zero, one or multiple dots in their names placed in a variable path which also may contain dots, it will start to get ugly. I have not provided an answer for this scenario as I think it is unlikely.
Edit: To also capture filenames without a path, replace the first part with (?:.*[\\/])?, which is an optional non-capturing group.
Does this work...
.*\/(.+)$
Posting here so I can get feedback
Here a solution to extract the file name without the dot of the extension.
I begin with the answer from #Hammad Khan and add the dot in the search character. So, dots can be part of the file name:
[ \w-.]+\.
Then use the regex look ahead(?= ) for a dot, so it will stop the search at the last dot (the dot before the extension), and the dot will not appears in the result:
[ \w-.]+(?=[.])
reorder, it's not necessary but look better:
[\w-. ]+(?=[.])
try this
[^\\]+$
you can also add extension for specificity
[^\\]+pdf$