214

I would like to change a file extension from *.txt to *.text. I tried using the basename command, but I'm having trouble on changing more than one file.

Here's my code:

files=`ls -1 *.txt`

for x in $files
do
    mv $x "`basename $files .txt`.text"
done

I'm getting this error:

basename: too many arguments Try basename --help' for more information

12 Answers 12

243

Straight from Greg's Wiki:

# Rename all *.txt to *.text
for f in *.txt; do 
    mv -- "$f" "${f%.txt}.text"
done

Also see the entry on why you shouldn't parse ls.

Edit: if you have to use basename, your syntax would be:

for f in *.txt; do
    mv -- "$f" "$(basename -- "$f" .txt).text"
done
| improve this answer | |
  • 67
    There's some slightly advanced Bash syntax used here without explanation. I had to Google many things to understand this fully. To help others in my position, here are some references about globs (the *.txt syntax), the bare double dash --, and the use of the % symbol within the ${} construct. Until now I'd never encountered the latter two and hadn't known what was really happening with the former. – Mark Amery Sep 21 '14 at 12:32
  • 4
    Look at Matthias Braun's answer below - much simpler, accomplishes the same thing. – btk Mar 10 '15 at 14:38
  • 27
    One liner for f in *.txt; do mv -- "$f" "${f%.txt}.text"; done – Marçal Juan Oct 21 '15 at 14:35
  • 2
    @PéturIngiEgilsson on the second line? No, it's not required. – jasonwryan Jan 28 '17 at 8:33
  • 5
    what does the -- stand for? – Calculus Oct 28 '18 at 5:25
170

Here's how I change all the file extensions in the current directory on Debian.

rename "s/oldExtension/newExtension/" *.txt

(This is the Perl rename command, not the util-linux one. See Why is the rename utility on Debian/Ubuntu different than the one on other distributions, like CentOS?)

| improve this answer | |
  • 11
    Much simpler than the accepted answer, thank you! – btk Mar 10 '15 at 14:37
  • 13
    On Mac I was able to brew install rename and get this to work. – Monkpit Jul 19 '16 at 14:42
  • 3
    with fish shell you can do rename "s/oldExtension/newExtension/" **.txt to rename all *.txt recursively – Matěj Šmíd Feb 16 '17 at 13:03
  • 4
    This will fail for footxt.txt. Use this instead: unix.stackexchange.com/questions/19654/… – wisbucky Aug 1 '17 at 23:57
  • 7
    When your files don't have any extension and you want to add one: rename 's/$/.txt/' * – mkataja Aug 2 '17 at 7:21
46

A simple command, the rename from util-linux, will do that for you, it replace every occurences of "txt" to "text" in all file matching "*.txt":

rename txt text *.txt
| improve this answer | |
  • 4
    rename changes the first occurrence, so better make that rename .txt .text, but this still won't always work (e.g. it renames foo.txtx.bar.txt to foo.textx.bar.txt). – Gilles 'SO- stop being evil' Aug 29 '11 at 21:28
  • 16
    It should be noted that not all systems have the same version of rename; on Debian and friends, the rename command is actually perl-rename and uses perl regexes. For that, the equivalent command would be: rename 's/.txt/.text/' *.txt. People should check the man rename on their system to find out which one they have. – evilsoup Nov 13 '13 at 15:12
  • @evilsoup That's the case for OSX, thx – Davi Lima Aug 17 '15 at 19:23
  • This also works on Cygwin (Windows) – andy Apr 12 '17 at 9:36
34
rename "s/oldExtension/newExtension/" *.txt

Above works fine but limited to current directory. Try the command below, which is flexible with sub-directories. It will rename all .txt files under directory structure with a new extension.

find . -name "*.txt" -exec rename 's/.txt$/.newext/' {} \;
| improve this answer | |
  • 2
    rename can handle multiple files as argument, you can vastly speed things up by using + instead of \; if there are many such files – Anthon Feb 17 '15 at 8:57
  • Still, it is a useful option since "*.txt" can unroll to a large argument list unsupported by the shell. – Roman Shapovalov Oct 19 '16 at 13:14
  • 1
    "s/oldExtension/newExtension/" will fail for footxt.txt. Use this instead: unix.stackexchange.com/questions/19654/… – wisbucky Aug 1 '17 at 23:59
15

The answers here referencing s/oldExtension/newExtension/ are wrong. If you use s/txt/text/, you would convert footxt.txt to footext.txt, which is not what you want. Even if you use s/.txt/.text/, that would convert footxt.txt to fo.text.txt.

You have to use \. to match the period (. will match any character). And the trailing $ to match the end of the line. Only this will properly match the extension.

rename 's/\.txt$/.text/' *.txt

rename 's/\.old$/.new/' *.old
| improve this answer | |
9
for f in *.txt
do
    [ -f "$f" ] && mv "$f" "${f%txt}text"
done
| improve this answer | |
8

Based on the @Prince John Wesley answer, here is a simple bash script for changing all extensions of files in the current directory from ext1 to ext2. Also outputs names of the files being renamed.

#!/bin/bash
for f in *.$1
do
    [ -f "$f" ] && mv -v "$f" "${f%$1}$2"
done

Example usage (assuming the name of the script is change-ext):

change-ext ext1 ext2
| improve this answer | |
  • To change extensions of files in directories recursively, replace the second line (for...) with two lines: shopt -s globstar and for f in **/*.$1. Requires Bash 4+. – Dennis Golomazov Feb 2 '12 at 10:40
7

Reason #53 to switch to zsh:

zmv '(*).txt' '$1.text'

| improve this answer | |
6

let's say your files are scattered in various directory, Assuming that dirx is your parent directory, this can do the job using find:

for f in `find /dirx -iname '*.txt' -type f -print`;do  mv "$f" ${f%.txt}.text; done
| improve this answer | |
3

When you

do not have an extension for the source files

and target extension is .text you would do it this way -

for f in *; do mv -- "$f" "${f%.\*}.text"; done
| improve this answer | |
  • 1
    Excellent bash-native script. worked great! for f in *.html; do mv -- "$f" "${f%.\*}.pug"; done – TamusJRoyce Jul 19 '19 at 17:54
2

This is what works for me:

find . -name '*.txt' -exec rename 's/\.txt$/.text/' \{} \;
| improve this answer | |
  • 1
    PLEASE add a . before the "text" – danielmhanover Jul 3 '17 at 7:10
1

In case you want to know what went wrong in your version: You used $files instead of $x in the basename command. So this should work (untested, though):

for x in *.txt
do
  mv "$x" "`basename '$x' .txt`.text"
done
| improve this answer | |

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