38

I have a bunch of files named like so:

output_1.png
output_2.png
...
output_10.png
...
output_120.png

What is the easiest way of renaming those to match a convention, e.g. with maximum four decimals, so that the files are named:

output_0001.png
output_0002.png
...
output_0010.png
output_0120.png

This should be easy in Unix/Linux/BSD, although I also have access to Windows. Any language is fine, but I'm interested in some really neat one-liners (if there are any?).

skaffman
  • 398,947
  • 96
  • 818
  • 769
slhck
  • 36,575
  • 28
  • 148
  • 201
  • Also remember that you should ensure that while you are renaming files that none are overwritten by file name conflicts. I recommend creating a temporary directory to move each file into with its new name and then moving all of the files back. – vincent gravitas Nov 05 '12 at 00:34
  • 1
    possible duplicate of [linux shell script to add leading zeros to file names](http://stackoverflow.com/questions/3672301/linux-shell-script-to-add-leading-zeros-to-file-names) – Serge Stroobandt Mar 04 '14 at 20:28
  • Possible duplicate of [Bash script to pad file names](http://stackoverflow.com/questions/55754/bash-script-to-pad-file-names) – Ciro Santilli OurBigBook.com Oct 01 '15 at 07:41

10 Answers10

56

Python

import os
path = '/path/to/files/'
for filename in os.listdir(path):
    prefix, num = filename[:-4].split('_')
    num = num.zfill(4)
    new_filename = prefix + "_" + num + ".png"
    os.rename(os.path.join(path, filename), os.path.join(path, new_filename))

you could compile a list of valid filenames assuming that all files that start with "output_" and end with ".png" are valid files:

l = [(x, "output" + x[7:-4].zfill(4) + ".png") for x in os.listdir(path) if x.startswith("output_") and x.endswith(".png")]

for oldname, newname in l:
    os.rename(os.path.join(path,oldname), os.path.join(path,newname))

Bash

(from: http://www.walkingrandomly.com/?p=2850)

In other words I replace file1.png with file001.png and file20.png with file020.png and so on. Here’s how to do that in bash

#!/bin/bash
num=`expr match "$1" '[^0-9]*\([0-9]\+\).*'`
paddednum=`printf "%03d" $num`
echo ${1/$num/$paddednum}

Save the above to a file called zeropad.sh and then do the following command to make it executable

chmod +x ./zeropad.sh

You can then use the zeropad.sh script as follows

./zeropad.sh frame1.png

which will return the result

frame001.png

All that remains is to use this script to rename all of the .png files in the current directory such that they are zeropadded.

for i in *.png;do mv $i `./zeropad.sh $i`; done

Perl

(from: Zero pad rename e.g. Image (2).jpg -> Image (002).jpg)

use strict;
use warnings;
use File::Find;

sub pad_left {
   my $num = shift;

   if ($num < 10) {
      $num = "00$num";
   }
   elsif ($num < 100) {
      $num = "0$num";
   }

   return $num;
}

sub new_name {
   if (/\.jpg$/) {
      my $name = $File::Find::name;
      my $new_name;
      ($new_name = $name) =~ s/^(.+\/[\w ]+\()(\d+)\)/$1 . &pad_left($2) .')'/e;
      rename($name, $new_name);
      print "$name --> $new_name\n";
   }
}

chomp(my $localdir = `pwd`);# invoke the script in the parent-directory of the
                            # image-containing sub-directories

find(\&new_name, $localdir);

Rename

Also from above answer:

rename 's/\d+/sprintf("%04d",$&)/e' *.png
Community
  • 1
  • 1
dting
  • 38,604
  • 10
  • 95
  • 114
  • In `prefix, num = filename[:-4].split('_')` I get: `ValueError: need more than 1 value to unpack` – slhck Mar 24 '11 at 11:15
  • Ah, the culprit is that it only works when the target directory doesn't contain any other files. It's nice, but still not very portable. I'll see if other people come up with some other solutions :) – slhck Mar 24 '11 at 11:18
  • I **also** have access to Windows. You could insert the bash script you just posted into your answer, I'd accept that. It's just that I like StackOverflow answers having the code already there, not just hyperlinking to some solution. – slhck Mar 24 '11 at 11:24
  • 1
    You should use `printf "%03d" $((10#${num}))` in bash script, otherwise "08" and "09" is not accepted. http://stackoverflow.com/questions/8078167/bizarre-issue-with-printf-in-bash-script09-and-08-are-invalid-numbers-07 – ironsand Sep 26 '13 at 17:38
  • @DTing the bash version seems to have trouble when certain files do not need to be renamed. For example `mv: 'test-100001.txt' and 'test-100001.txt' are the same file` is returned when mv has no work to do. (it was already test-%06d.txt in my case) Does this pose any issue or can it just be a matter of suppressing error messages? – kevinkayaks Sep 04 '18 at 23:05
  • Pretty damn amazing answer. I'm actually even a bit inclined to write it in Ruby just to add as a comment! Seriously, great job. – Fernando Cordeiro May 11 '20 at 16:20
13

Fairly easy, although it combines a few features not immediately obvious:

@echo off
setlocal enableextensions enabledelayedexpansion
rem iterate over all PNG files:
for %%f in (*.png) do (
    rem store file name without extension
    set FileName=%%~nf
    rem strip the "output_"
    set FileName=!FileName:output_=!
    rem Add leading zeroes:
    set FileName=000!FileName!
    rem Trim to only four digits, from the end
    set FileName=!FileName:~-4!
    rem Add "output_" and extension again
    set FileName=output_!FileName!%%~xf
    rem Rename the file
    rename "%%f" "!FileName!"
)

Edit: Misread that you're not after a batch file but any solution in any language. Sorry for that. To make up for it, a PowerShell one-liner:

gci *.png|%{rni $_ ('output_{0:0000}.png' -f +($_.basename-split'_')[1])}

Stick a ?{$_.basename-match'_\d+'} in there if you have other files that do not follow that pattern.

Joey
  • 344,408
  • 85
  • 689
  • 683
8

I actually just needed to do this on OSX. Here's the scripts I created for it - single line!

> for i in output_*.png;do mv $i `printf output_%04d.png $(echo $i | sed 's/[^0-9]*//g')`; done
Adam
  • 4,159
  • 4
  • 32
  • 53
  • Thanks for this. I had some gotchyas though relating to input files that had numbers within their suffix (E.g., .mp3) and spaces within the input file names. I'd better elaborate in my own answer, as comments don't permit any formatting. – Gurce Feb 01 '18 at 10:25
3

For mass renaming the only safe solution is mmv—it checks for collisions and allows renaming in chains and cycles, something that is beyond most scripts. Unfortunately, zero padding it ain't too hot at. A flavour:

c:> mmv output_[0-9].png output_000#1.png

Here's one workaround:

c:> type file
mmv
[^0-9][0-9] #1\00#2
[^0-9][0-9][^0-9] #1\00#2#3
[^0-9][0-9][0-9] #1\0#2#3
[^0-9][0-9][0-9][^0-9] #1\0#2#3
c:> mmv <file
bobbogo
  • 14,989
  • 3
  • 48
  • 57
2

Here is a Python script I wrote that pads zeroes depending on the largest number present and ignores non-numbered files in the given directory. Usage:

python ensure_zero_padding_in_numbering_of_files.py /path/to/directory

Body of script:

import argparse
import os
import re
import sys

def main(cmdline):

    parser = argparse.ArgumentParser(
        description='Ensure zero padding in numbering of files.')
    parser.add_argument('path', type=str,
        help='path to the directory containing the files')
    args = parser.parse_args()
    path = args.path

    numbered = re.compile(r'(.*?)(\d+)\.(.*)')

    numbered_fnames = [fname for fname in os.listdir(path)
                       if numbered.search(fname)]

    max_digits = max(len(numbered.search(fname).group(2))
                     for fname in numbered_fnames)

    for fname in numbered_fnames:
        _, prefix, num, ext, _  = numbered.split(fname, maxsplit=1)
        num = num.zfill(max_digits)
        new_fname = "{}{}.{}".format(prefix, num, ext)
        if fname != new_fname:
            os.rename(os.path.join(path, fname), os.path.join(path, new_fname))
            print "Renamed {} to {}".format(fname, new_fname)
        else:
            print "{} seems fine".format(fname)

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
Donny Winston
  • 2,324
  • 1
  • 15
  • 13
1
$rename output_ output_0 output_?   # adding 1 zero to names ended in 1 digit
$rename output_ output_0 output_??  # adding 1 zero to names ended in 2 digits
$rename output_ output_0 output_??? # adding 1 zero to names ended in 3 digits

That's it!

1

with bash split,

linux

for f in *.png;do n=${f#*_};n=${n%.*};mv $f $(printf output_"%04d".png $n);done

windows(bash)

for f in *.png;do n=${f#*_};mv $f $(printf output_"%08s" $n);done
jns
  • 251
  • 2
  • 4
0

I'm following on from Adam's solution for OSX.

Some gotchyas I encountered in my scenario were:

  1. I had a set of .mp3 files, so the sed was catching the '3' in the '.mp3' suffix. (I used basename instead of echo to rectify this)
  2. My .mp3's had spaces within their names, E.g., "audio track 1.mp3", this was causing basename+sed to screw up a little bit, so I had to quote the "$i" parameter.

In the end, my conversion line looked like this:

for i in *.mp3 ; do mv "$i" `printf "track_%02d.mp3\n" $(basename "$i" .mp3 | sed 's/[^0-9]*//g')` ; done
Gurce
  • 592
  • 7
  • 18
0

Using ls + awk + sh:

ls -1 | awk -F_ '{printf "%s%04d.png\n", "mv "$0" "$1"_", $2}' | sh

If you want to test the command before runing it just remove the | sh

SergioAraujo
  • 11,069
  • 3
  • 50
  • 40
0

I just want to make time lapse movie using

ffmpeg  -pattern_type glob -i "*.jpg" -s:v 1920x1080 -c:v libx264 output.mp4 

and got a similar problem.

[image2 @ 000000000039c300] Pattern type 'glob' was selected but globbing is not supported by this libavformat build

glob not support on Windows 7 . Also if file list like below, and uses %2d.jpg or %02d.jpg

1.jpg 2.jpg ... 10.jpg 11.jpg ...

[image2 @ 00000000005ea9c0] Could find no file with path '%2d.jpg' and index in the range 0-4  
%2d.jpg: No such file or directory 
[image2 @ 00000000005aa980] Could find no file with path '%02d.jpg' and index in the range 0-4  
%02d.jpg: No such file or directory

here is my batch script to rename flies

@echo off
setlocal enabledelayedexpansion

set i=1000000
set X=1
for %%a in (*.jpg) do (
    set /a i+=1
    set "filename=!i:~%X%!"
    echo ren "%%a" "!filename!%%~xa"
    ren "%%a" "!filename!%%~xa"
)

after rename 143,323 jpg files,

ffmpeg -i %6d.jpg -s:v 1920x1080 -c:v libx264 output.mp4 
SeamanYen
  • 44
  • 4