Using find
and the perl rename
utility (AKA prename
, file-rename
, perl-rename
depending on what distro you are using), plus the Date::Parse and Date::Format library modules.
These modules are not included with perl, they need to be installed separately, either via the cpan command-line tool or a distro package. They are both in the TimeDate bundle - on Debian they can be installed with sudo apt-get install libtimedate-perl
. I don't know what the package is named on other distros, but it's probably very similar.
BTW, the perl rename
utility is not to be confused with the rename
utility from util-linux
which has completely different and incompatible capabilities and command-line options.
Anyway:
$ mkdir 'April 1, 2004' 'Amsterdam, September 18, 2018' 'Anderson Arbor, March 24, 2011'
$ find . -type d -print0 |
rename -n -0 'BEGIN {
use Date::Parse;
use Date::Format;
our $date_pattern = qr/[[:alpha:]]+ \d+, \d\d\d\d/;
};
our $date_pattern;
if (/^(.*\/)(.*)\b($date_pattern)$/) {
my ($p1, $p2, $d) = ($1, $2, $3);
my $t = str2time($d);
s/\b$date_pattern//;
$_ = sprintf("%s%s%s", $p1, time2str("%Y-%m-%d ",$t), $p2);
s/[, ]*$//;
}'
rename(./April 1, 2004, ./2004-04-01)
rename(./Amsterdam, September 18, 2018, ./2018-09-18 Amsterdam)
rename(./Anderson Arbor, March 24, 2011, ./2011-03-24 Anderson Arbor)
NOTE: This is a dry-run due to the -n
option. Once you're sure it does what you want, remove the -n
or replace it with -v
for verbose output.
After loading the Date::Parse and Date::Format modules and defining a variable ($date_pattern
) in the BEGIN {}
block, this rename script iterates over each directory name and attempts to match the "Month Day, Year" pattern.
If successful then all but the final element of the pathname is captured/extracted into variable $p1
, any "prefix" portion of the final directory element before the date pattern into variable $p2
(this is optional and, if missing, will be an empty string), and the date pattern into variable $d
.
The matched date $d
is then converted to a time_t
value (seconds since the epoch, Midnight Jan 1 1970) and assigned to variable $t
using the str2time()
function.
Then:
The date pattern is stripped from the directory name
The sprintf
and time2str()
function are used to modify the entire filename to conform to the new pattern by changing $_
.
This simple assignment to $_
highlights a very simple but significant fact about how perl rename
works.
In perl, the variable $_
is used as the default variable in loops and as the default argument/operand of many functions & operators if no other variable name is specified (See man perlvar
and search for $_
), including the s///
operator.
With perl rename, $_
is used for the current path/filename and anything that changes $_
will result in it being renamed. If $_
is changed, it will be renamed. If it isn't changed, it won't be.
Note that rename
won't rename a file or directory over an existing filename unless you force it to with the -f
option.
All trailing spaces and commas (if any) are then stripped from the final directory name.
Other Notes:
The $date_pattern
variable is defined in the BEGIN{}
block so that it is only executed once when the script starts (rather than once for every directory name on stdin). It's defined as a variable because it's used twice - if it ever needs to be changed, it's better to only need to change it in one place. It is defined as "one-or-more alphabetic characters, a space, one-or-more digits, another space, and four digits". This matches your example directory names, but may need to be adjusted if other variants are possible.
It is declared with our
so that it is valid for the entire scope of the rename script (otherwise it will only be available inside the BEGIN block). perl rename scripts run in perl's strict
mode (see perldoc strict
for details).
qr
is a perl quoting operator that quotes (and potentially pre-compiles, for performance) regular expressions - performance probably isn't a problem here, I've used it here mainly to work around the fact that it's a pain to use single-quotes inside a single-quoted one-liner script. See perldoc -f qr
for details.
by using find ... -print0
and rename
's -0
option, NUL is used as the filename separator, so this will work with any directory name, even those containing newlines and other whitespace or shell meta-characters such as ;
, &
, >
etc. NUL is the only character which can't be in a pathname.