307

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
1

14 Answers 14

380

Straight from Greg's Wiki:

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

*.txt is a globbing pattern, using * as a wildcard to match any string. *.txt matches all filenames ending with '.txt'.

-- marks the end of the option list. This avoids issues with filenames starting with hyphens.

${f%.txt} is a parameter expansion, replaced by the value of the f variable with .txt removed from the end.

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

If you have to use basename, your syntax would be:

for f in *.txt; do
    mv -- "$f" "$(basename -- "$f" .txt).text"
done
1
  • 51
    One liner for f in *.txt; do mv -- "$f" "${f%.txt}.text"; done Oct 21 '15 at 14:35
206

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?)

On MacOS, user Monkpit reports that they were able to use brew install rename to get this to work.

4
  • It works on Ubuntu. Jan 19 '17 at 0:20
  • 4
    with fish shell you can do rename "s/oldExtension/newExtension/" **.txt to rename all *.txt recursively Feb 16 '17 at 13:03
  • 10
    When your files don't have any extension and you want to add one: rename 's/$/.txt/' *
    – mkataja
    Aug 2 '17 at 7:21
  • This would be ideal, rather than the chosen best answer. Anyway, note that different linux distros have different implementations of rename; e.g. OpenSuse uses util-linux.rename and won't work with regular-expressions (meh.. 😒), so not ideal for file-extension renaming. Jul 12 '18 at 10:19
64

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
4
  • 7
    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). Aug 29 '11 at 21:28
  • 19
    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
  • 1
    @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
48
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 . -type f -name "*.txt" -exec rename 's/\.txt$/.newext/' '{}' \;
3
  • 3
    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. 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
29

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
15

Reason #53 to switch to zsh:

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

2
  • 1
    For Mac, I have to run this command "autoload -U zmv". Otherwise, I am getting "zsh: command not found: zmv" Oct 7 at 15:35
  • I'm guessing autoload -U zmv means, on "my user profile" autoload zmv. Thanks a bunch for this =) Nov 16 at 5:05
12

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
1
  • 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+. Feb 2 '12 at 10:40
11
for f in *.txt
do
    [ -f "$f" ] && mv "$f" "${f%txt}text"
done
0
10

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
1
8

On Ubuntu 18.04, the util-linux rename command is available as rename.ul. This worked for me:

rename.ul -o -v .oldext .newext *.oldext

Options:

  • -o: don't overwrite preexisting .newext
  • -v: verbose
  • -n: dry run

For more info, see man rename.ul or rename.ul -h.

5

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
1
  • 1
    Excellent bash-native script. worked great! for f in *.html; do mv -- "$f" "${f%.\*}.pug"; done Jul 19 '19 at 17:54
3

This is what works for me:

find . -name '*.txt' -exec rename 's/\.txt$/.text/' \{} \;
0
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
0
0

Mmv (available in the main distributions repositories) is also very useful for renaming. Give the patterns in quotes and each glob element can be reproduced by #N:

mmv '*.txt' '#1.text'

Some more interesting, neat examples in the manual page:

Rename files ending in .html.en, .html.de, etc. to ending in .en.html, .de.html, etc.:

mmv '*.html.??' '#1.#2#3.html' 

Rename music files from <track no.> - <interpreter> - <song title>.ogg to <interpreter> - <track no.> - <song title>.ogg:

mmv '* - * - *.ogg' '#2 - #1 - #3.ogg' 

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