Padding Numbers In Filenames
file1.txt file100.txt file2.txt … is hardly ordered and regular. So pattern‑match‑pad‑replace.
TL;DR
for name in *.txt; do
mv "${name}" \
"${name/(#b)(<->)/${(l:3::0:)match}}"
done
Numbered filenames without padding
Filenames with numbers in them are not uncommon. But sorting them always has an unexpected conclusion.
file1.txt
file100.txt
file2.txt
file200.txt
This is because sorting is an operation based on individual character codes rather than words or numbers which are both strings of characters (or digits).
Pad it
The well‑known solution is to rename the files such that the numbers are all of the same length. So if the numbers go from 1 to 100, all the numbers have to be three digits long
file001.txt
file002.txt
file100.txt
file200.txt
But that’s a slow process, so obviously one has to do it faster
Pad it programmatically
NB These are instructions for zsh and most likely will not work in other shells
Hence the expanded parameter
${name/(#b)(<->)/${l:3::0:)match}}
that is broken down.
substitution
${name/pattern/replacement}
Overall, it is a substituition. ${name}
is the parameter
(assuming name
holds a filename). zsh will look for
the first occurence of pattern
in name
and replace it with the value of replacement
.
$ name='John Doe'
$ echo "${name/o/u}"
Juhn Doe
$ echo "${name/oe/onne}"
John Donne
number pattern
The search pattern (#b)(<->)
has two parts. Let’s first look at the
<->
.
<n-m>
is a glob pattern in zsh to indicate a range of numbers—
not digit characters. This is useful because the more common [0-9]
pattern only matches one digit. But numbers in filenames are longer.
The pattern <->
is shorthand to mean any number.
$ name='May 1968'
$ echo "${name/<->/XXXX}"
May XXXX
Once the shell option extendedglob
is enabled in zsh, the other part (#b)
enables back-referencing for all subsequent patterns
enclosed in parantheses. Back-referencing stores the matched pattern in a
variable called match
.
Hence (#b)(<->)
finds a number and then stores it in a variable called
match
.
padded replacement
The ${(l:3::0:)match}
which is used as the replacement in the main expansion
is essentially ${match}
—the matched number from earlier—with a
qualifier enclosed in parentheses.
The (l:3::0:)
qualifier to a parameter pads the paramater to 3 characters on the left
with the character 0.
(l:3:)
would truncate or pad a parameter as needed to make it three characters long.
By default, padding is done using blank spaces, but in ::0
we specify to use zeros.
pattern‑match‑pad‑replace
Bringing it all together, we have
${name/(#b)(<->)/${(l:3::0:)name}}
in a for-loop