I have a lot of files that are named as: MM-DD-YYYY.pdf
. I want to rename them as YYYY-MM-DD.pdf
I’m sure there is some bash magic to do this. What is it?
-
5What did you try for yourself? We can assist you with your efforts, not provide a whole solution – Inian May 10 '18 at 08:58
-
@jlconlin: Have a safe flight and check out our answers after your arrival! ;-) – Allan May 10 '18 at 09:31
4 Answers
For files in the current directory:
for name in ./??-??-????.pdf; do
if [[ "$name" =~ (.*)/([0-9]{2})-([0-9]{2})-([0-9]{4})\.pdf ]]; then
echo mv "$name" "${BASH_REMATCH[1]}/${BASH_REMATCH[4]}-${BASH_REMATCH[3]}-${BASH_REMATCH[2]}.pdf"
fi
done
Recursively, in or under the current directory:
find . -type f -name '??-??-????.pdf' -exec bash -c '
for name do
if [[ "$name" =~ (.*)/([0-9]{2})-([0-9]{2})-([0-9]{4})\.pdf ]]; then
echo mv "$name" "${BASH_REMATCH[1]}/${BASH_REMATCH[4]}-${BASH_REMATCH[3]}-${BASH_REMATCH[2]}.pdf"
fi
done' bash {} +
Enabling the globstar
shell option in bash
lets us do the following (will also, like the above solution, handle all files in or below the current directory):
shopt -s globstar
for name in **/??-??-????.pdf; do
if [[ "$name" =~ (.*)/([0-9]{2})-([0-9]{2})-([0-9]{4})\.pdf ]]; then
echo mv "$name" "${BASH_REMATCH[1]}/${BASH_REMATCH[4]}-${BASH_REMATCH[3]}-${BASH_REMATCH[2]}.pdf"
fi
done
All three of these solutions uses a regular expression to pick out the relevant parts of the filenames, and then rearranges these parts into the new name. The only difference between them is how the list of pathnames is generated.
The code prefixes mv
with echo
for safety. To actually rename files, remove the echo
(but run at least once with echo
to see that it does what you want).

- 14,885
- 3
- 41
- 52
-
1@Inian Thanks! The glob is only there for selecting filenames. In the find variation, you could even delete the -name bit completely, while in the first variation, you might as well glob on *.pdf. I did as I did to show that the patterns were related. As for efficiency, I highly doubt it will make a noticeable difference. – Kusalananda May 10 '18 at 12:21
-
`for name in **/??-??-????.pdf` would be simpler than using `find` in this case, assuming the OP isn't stuck with `bash` 3.x. – chepner May 10 '18 at 12:38
-
@chepner With extglob enabled, yes. I might mention this later (on phone now). – Kusalananda May 10 '18 at 12:45
-
This is great! (Sorry for the slow response.) Unfortunately, I can't use `globstar`. Apparently my bash is too old on my Mac. So where does `BASH_REMATCH` know where to get the regex groups from? – jlconlin May 12 '18 at 14:47
A direct approach example from the command line:
$ ls
10-01-2018.pdf 11-01-2018.pdf 12-01-2018.pdf
$ ls [0-9]*-[0-9]*-[0-9]*.pdf|sed -r 'p;s/([0-9]{2})-([0-9]{2})-([0-9]{4})/\3-\1-\2/'|xargs -n2 mv
$ ls
2018-10-01.pdf 2018-11-01.pdf 2018-12-01.pdf
The ls
output is piped to sed
, then we use the p flag to print the argument without modifications, in other words, the original name of the file, and s to perform and output the conversion.
The ls
+ sed
result is a combined output that consist of a sequence of old_file_name and new_file_name.
Finally we pipe the resulting feed through xargs
to get the effective rename of the files.
From xargs
man:
-n number Execute command using as many standard input arguments as possible, up to number arguments maximum.

- 14,447
- 2
- 38
- 52
You can use the following command very close to the one of klashxx:
for f in *.pdf; do echo "$f"; mv "$f" "$(echo "$f" | sed 's@\(..\)-\(..\)-\(....\)@\3-\2-\1@')"; done
before:
ls *.pdf
12-01-1998.pdf 12-03-2018.pdf
after:
ls *.pdf
1998-01-12.pdf 2018-03-12.pdf
Also if you have other pdf
files that does not respect this format in your folder, what you can do is to select only the files that respect the format: MM-DD-YYYY.pdf
to do so use the following command:
for f in `find . -maxdepth 1 -type f -regextype sed -regex './[0-9]\{2\}-[0-9]\{2\}-[0-9]\{4\}.pdf' | xargs -n1 basename`; do echo "$f"; mv "$f" "$(echo "$f" | sed 's@\(..\)-\(..\)-\(....\)@\3-\2-\1@')"; done
Explanations:
find . -maxdepth 1 -type f -regextype sed -regex './[0-9]\{2\}-[0-9]\{2\}-[0-9]\{4\}.pdf
this find command will look only for files in the current working directory that respect your syntax and extract their basename (remove the./
at the beginning, folders and other type of files that would have the same name are not taken into account, other*.pdf
files are also ignored.for each file
you do a move and the resulting file name is computed using sed and back reference to the 3 groups for MM,DD and YYYY

- 12,117
- 3
- 27
- 51
For these simple filenames, using a more verbose pattern, you can simplify the body of the loop a bit:
twodigit=[[:digit:]][[:digit:]]
fourdigit="$twodigit$twodigit"
for f in $twodigit-$twodigit-$fourdigit.pdf; do
IFS=- read month day year <<< "${f%.pdf}"
mv "$f" "$year-$month-$day.pdf"
done
This is basically @Kusalananda's answer, but without the verbosity of regular-expression matching.

- 497,756
- 71
- 530
- 681