I have a directory full of files. I want to rename all the files that match *.py to -backup.py. How do I do that.

I know I can use for i in *.py, but from there I'm not sure how to keep the initial name and just append backup to all of them.

for i in *.py
    mv "$i" `echo "$i" | sed 's/\.py$/-backup.py/'`
  • Thanks for the help. Not to impose, but could you also explain what's going on so I can implement similar solutions when this problem comes across? – dasElevator Jul 2 '13 at 20:06
  • Interesting...I might have used something with find so that on a HUGE directory I would hit the command line length limit. – mdpc Jul 2 '13 at 20:28
  • @dasElevator The for statement picks one file at a time that matches your criterion (.py extension) in the directory. The mv statement renames it. The last part of the mv uses sed to generate the name as per your specs. – unxnut Jul 2 '13 at 20:32

The same as evilsoup with find:

find . -name '*.py' -exec bash -c 'mv "$1" "${1%.py}-backup.py"' _ {} \;

or if you don't like to launch an instance of for each file found:

find . -name '*.py' -exec bash -c 'for f; do mv "$f" "${f%.py}-backup.py"; done' _ {} +

The find solution might be better if you have a huge number of files, since globbing is slow. Feel free to add an -n option to mv (no clobber) or a -v option (verbose).

  • More importantly than globbing being slow vs find (though ideally I'd like to see something backing that up), if you are working with a truly huge number of files, with globs you may run into the maximum number of arguments for bash. So if you're working with many hundreds of thousands of files, this is the way to go. (You can find out what the maximum number of arguments is on your system with echo "$(getconf ARG_MAX)/4-1" | bc). This is also POSIX (or it would be, if you replaced bash -c with sh -c), whereas globstar isn't. – evilsoup Jul 2 '13 at 22:05

The essence of this is the same as unxnut's answer, but I think using sed for something as simple as stripping an extension is way overkill:

for f in ./*.py; do mv "$f" "${f%.py}-backup.py"; done

${f%.py} is the variable $f, with the .py stripped from the end. You can also remove things from the beginning with ${f#whatever}. For recursiveness (i.e. working in the current directory and all subdirectories), assuming you have bash 4+, you can use globstar:

shopt -s globstar
for f in ./**/*.py; do mv "$f" "${f%.py}-backup.py"; done

Alternatively, you can use rename -- there are actually two different programs that use this name, and which one you have will depend on your flavour of *nix. perl-rename (used by Debian and its derivatives, such as Ubuntu, and many others):

rename 's/\.py$/-rename.py/' ./*.py

The other rename would use this syntax:

rename .py -backup.py ./*.py

The for loop with mv is more portable, of course: the first, non-recursive one is POSIX, and so will work on every *nix anywhere, except maybe some museum pieces.

Not the answer you're looking for? Browse other questions tagged or ask your own question.