How to zero pad numbers in file names in Bash?
Asked Answered
V

10

66

What is the best way, using Bash, to rename files in the form:

(foo1, foo2, ..., foo1300, ..., fooN)

With zero-padded file names:

(foo00001, foo00002, ..., foo01300, ..., fooN)
Veer answered 11/9, 2008 at 3:25 Comment(0)
U
44

In case N is not a priori fixed:

for f in foo[0-9]*; do
  mv "$f" "$(printf 'foo%05d' "${f#foo}")"
done
Uela answered 11/9, 2008 at 3:47 Comment(3)
spent a solid hour looking an applicable solution this morning, and this by far the "best" one I came across, ie. working on Linux, macOS, and no dependency on the rename utility. On side note, this solution did strip the "file extensions" for the files I was working with, so after applying this solution, a for f in *; do mv "$f" "$f.ext"; done was a quick band-aid® to get the desired extension back onto the files I was working with. πŸ‘ – Juta
This hiccups on files named 050 (for example: a number beginning with a zero is interpreted as octal base), outputting 00040 (not the expected 00050). See my answer, below. – Xeric
This stops with a question if your files are just foo1 and foo01. – Nd
S
81

It's not pure bash, but much easier with the Perl version of rename:

rename 's/\d+/sprintf("%05d",$&)/e' foo*

Where 's/\d+/sprintf("%05d",$&)/e' is the Perl replace regular expression.

  • \d+ will match the first set of numbers (at least one number)
  • sprintf("%05d",$&) will pass the matched numbers to Perl's sprintf, and %05d will pad to five digits
Suzy answered 30/12, 2010 at 10:23 Comment(2)
It handles filenames with spaces too. – Brittni
Note: rename != rename https://mcmap.net/q/55572/-using-regex-with-rename-version-from-util-linux – Mcmurry
U
44

In case N is not a priori fixed:

for f in foo[0-9]*; do
  mv "$f" "$(printf 'foo%05d' "${f#foo}")"
done
Uela answered 11/9, 2008 at 3:47 Comment(3)
spent a solid hour looking an applicable solution this morning, and this by far the "best" one I came across, ie. working on Linux, macOS, and no dependency on the rename utility. On side note, this solution did strip the "file extensions" for the files I was working with, so after applying this solution, a for f in *; do mv "$f" "$f.ext"; done was a quick band-aid® to get the desired extension back onto the files I was working with. πŸ‘ – Juta
This hiccups on files named 050 (for example: a number beginning with a zero is interpreted as octal base), outputting 00040 (not the expected 00050). See my answer, below. – Xeric
This stops with a question if your files are just foo1 and foo01. – Nd
W
23

I had a more complex case where the file names had a postfix as well as a prefix. I also needed to perform a subtraction on the number from the filename.

For example, I wanted foo56.png to become foo00000055.png.

I hope this helps if you're doing something more complex.

#!/bin/bash

prefix="foo"
postfix=".png"
targetDir="../newframes"
paddingLength=8

for file in ${prefix}[0-9]*${postfix}; do
  # strip the prefix off the file name
  postfile=${file#$prefix}
  # strip the postfix off the file name
  number=${postfile%$postfix}
  # subtract 1 from the resulting number
  i=$((number-1))
  # copy to a new name with padded zeros in a new folder
  cp ${file} "$targetDir"/$(printf $prefix%0${paddingLength}d$postfix $i)
done
Warily answered 13/9, 2010 at 11:54 Comment(0)
E
9

Pure Bash, no external processes other than 'mv':

for file in foo*; do
  newnumber='00000'${file#foo}      # get number, pack with zeros
  newnumber=${newnumber:(-5)}       # the last five characters
  mv $file foo$newnumber            # rename
done
Ecesis answered 3/2, 2009 at 8:43 Comment(0)
X
8

To left-pad numbers in filenames (here numeric file names with alphabetic extensions, e.g. 1.abc):

$ ls -l
total 0
-rw-r--r-- 1 victoria victoria 0 Mar 28 17:24 010
-rw-r--r-- 1 victoria victoria 0 Mar 28 18:09 050
-rw-r--r-- 1 victoria victoria 0 Mar 28 17:23 050.zzz
-rw-r--r-- 1 victoria victoria 0 Mar 28 17:24 10
-rw-r--r-- 1 victoria victoria 0 Mar 28 17:23 1.zzz

$ for f in [0-9]*.[a-z]*; do tmp=`echo $f | awk -F. '{printf "%04d.%s\n", $1, $2}'`; mv "$f" "$tmp"; done;

$ ls -l
total 0
-rw-r--r-- 1 victoria victoria 0 Mar 28 17:23 0001.zzz
-rw-r--r-- 1 victoria victoria 0 Mar 28 17:23 0050.zzz
-rw-r--r-- 1 victoria victoria 0 Mar 28 17:24 010
-rw-r--r-- 1 victoria victoria 0 Mar 28 18:09 050
-rw-r--r-- 1 victoria victoria 0 Mar 28 17:24 10

Explanation

for f in [0-9]*.[a-z]*; do tmp=`echo $f | \
awk -F. '{printf "%04d.%s\n", $1, $2}'`; mv "$f" "$tmp"; done;
  • note the backticks: `echo ... $2}\` (The backslash, \, immediately above just splits that one-liner over two lines for readability)
  • in a loop find files that are named as numbers with lowercase alphabet extensions: [0-9]*.[a-z]*
  • echo that filename ($f) to pass it to awk
  • -F. : awk field separator, a period (.): if matched, separates the file names as two fields ($1 = number; $2 = extension)
  • format with printf: print first field ($1, the number part) as 4 digits (%04d), then print the period, then print the second field ($2: the extension) as a string (%s). All of that is assigned to the $tmp variable
  • lastly, move the source file ($f) to the new filename ($tmp)
Xeric answered 29/3, 2019 at 1:7 Comment(1)
Thanks Victoria, this is the answer that helped me the most! Great explanation! – Dutiable
G
7

The oneline command that I use is this:

ls * | cat -n | while read i f; do mv "$f" `printf "PATTERN" "$i"`; done

PATTERN can be for example:

  • rename with increment counter: %04d.${f#*.} (keep original file extension)
  • rename with increment counter with prefix: photo_%04d.${f#*.} (keep original extension)
  • rename with increment counter and change extension to jpg: %04d.jpg
  • rename with increment counter with prefix and file basename: photo_$(basename $f .${f#*.})_%04d.${f#*.}
  • ...

You can filter the file to rename with for example ls *.jpg | ...

You have available the variable f that is the file name and i that is the counter.

For your question the right command is:

ls * | cat -n | while read i f; do mv "$f" `printf "foo%d05" "$i"`; done
Giotto answered 8/8, 2017 at 13:22 Comment(0)
O
2

The following will do it:

for ((i=1; i<=N; i++)) ; do mv foo$i `printf foo%05d $i` ; done

EDIT: changed to use ((i=1,...)), thanks mweerden!

Offhand answered 11/9, 2008 at 3:41 Comment(2)
Instead of using seq I would suggest writing for ((i=1; i<=N; i++)); do etc. Besides being part of bash, this also avoids having to first generate all numbers and then executing the for. – Campground
You don't need to use a subshell to invoke printf. print -v var will store the result in var variable. – Rudolph
I
1

My solution replaces numbers, everywhere in a string

for f in * ; do
    number=`echo $f | sed 's/[^0-9]*//g'`
    padded=`printf "%04d" $number`
    echo $f | sed "s/${number}/${padded}/";
done

You can easily try it, since it just prints transformed file names (no filesystem operations are performed).

Explanation:

Looping through list of files

A loop: for f in * ; do ;done, lists all files and passes each filename as $f variable to loop body.

Grabbing the number from string

With echo $f | sed we pipe variable $f to sed program.

In command sed 's/[^0-9]*//g', part [^0-9]* with modifier ^ tells to match opposite from digit 0-9 (not a number) and then remove it it with empty replacement //. Why not just remove [a-z]? Because filename can contain dots, dashes etc. So, we strip everything, that is not a number and get a number.

Next, we assign the result to number variable. Remember to not put spaces in assignment, like number = …, because you get different behavior.

We assign execution result of a command to variable, wrapping the command with backtick symbols `.

Zero padding

Command printf "%04d" $number changes format of a number to 4 digits and adds zeros if our number contains less than 4 digits.

Replacing number to zero-padded number

We use sed again with replacement command like s/substring/replacement/. To interpret our variables, we use double quotes and substitute our variables in this way ${number}.


The script above just prints transformed names, so, let's do actual renaming job:

for f in *.js ; do
    number=`echo $f | sed 's/[^0-9]*//g'`
    padded=`printf "%04d" $number`
    new_name=`echo $f | sed "s/${number}/${padded}/"`
    mv $f $new_name;
done

Hope this helps someone.

I spent several hours to figure this out.

Iridize answered 26/2, 2019 at 15:17 Comment(1)
This may stop with an overwrite question even with only two files, e.g., file1.js and file01.js. – Nd
L
1

This answer is derived from Chris Conway's accepted answer but assumes your files have an extension (unlike Chris' answer). Just paste this (rather long) one liner into your command line.

for f in foo[0-9]*; do mv "$f" "$(printf 'foo%05d' "${f#foo}" 2> /dev/null)"; done; for f in foo[0-9]*; do mv "$f" "$f.ext"; done;

OPTIONAL ADDITIONAL INFO

This script will rename

foo1.ext    > foo00001.ext
foo2.ext    > foo00002.ext
foo1300.ext > foo01300.ext

To test it on your machine, just paste this one liner into an EMPTY directory.

rm * 2> /dev/null; touch foo1.ext foo2.ext foo1300.ext; for f in foo[0-9]*; do mv "$f" "$(printf 'foo%05d' "${f#foo}" 2> /dev/null)"; done; for f in foo[0-9]*; do mv "$f" "$f.ext"; done;

This deletes the content of the directory, creates the files in the above example and then does the batch rename.

For those who don't need a one liner, the script indented looks like this.

for f in foo[0-9]*;
  do mv "$f" "$(printf 'foo%05d' "${f#foo}" 2> /dev/null)";
done;

for f in foo[0-9]*;
  do mv "$f" "$f.ext";
done;
Lutetium answered 3/9, 2021 at 1:59 Comment(0)
V
0

Here's a quick solution that assumes a fixed length prefix (your "foo") and fixed length padding. If you need more flexibility, maybe this will at least be a helpful starting point.

#!/bin/bash

# some test data
files="foo1
foo2
foo100
foo200
foo9999"

for f in $files; do
    prefix=`echo "$f" | cut -c 1-3`        # chars 1-3 = "foo"
    number=`echo "$f" | cut -c 4-`         # chars 4-end = the number
    printf "%s%04d\n" "$prefix" "$number"
done
Vittorio answered 11/9, 2008 at 3:43 Comment(0)

© 2022 - 2024 β€” McMap. All rights reserved.