346

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
3
  • Some ideas at peteryu.ca/tutorials/shellscripting/batch_rename too
    – Nemo
    Oct 14, 2015 at 13:22
  • You can use find: find . -iname '*.txt' -exec bash -c 'mv -- "$1" "${1%.txt}.text"' bash {} \;
    – Shayan
    Mar 9 at 17:35
  • This script is ok, but you put $files (the full list) as parameter of basename instead of $x (the current file); the line should be mv $x "`basename $x .txt`.text".
    – Fjor
    Jun 6 at 22:43

15 Answers 15

428

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
3
  • 60
    One liner for f in *.txt; do mv -- "$f" "${f%.txt}.text"; done Oct 21, 2015 at 14:35
  • For those who found this answer, be warned that if you mistakenly replace curly braces by parentheses in the "parameter expansion" syntax, it would remove your file. Don't make the same mistake as I did (luckily i used a test dir).
    – Long
    Apr 3 at 14:29
  • Sharing my experience on using this answer. I used directly these lines in my shell script to rename the file. I just replaced for f in *.txt with my file path. cd /u1/test/myfiles for f in /u1/test/myfiles mv -- "$f" "$(basename -- "$f" .txt).text" done Above line worked well and rename the files correctly, but there is a catch. It won’t rename and copy at /u1/test/myfiles same location. It will copy at last location of your shell script. The path which we can see using pwd command. We just need to cd on path first. Jun 15 at 13:04
219

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, 2017 at 0:20
  • 6
    with fish shell you can do rename "s/oldExtension/newExtension/" **.txt to rename all *.txt recursively Feb 16, 2017 at 13:03
  • 10
    When your files don't have any extension and you want to add one: rename 's/$/.txt/' *
    – mkataja
    Aug 2, 2017 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, 2018 at 10:19
72

A simple command, rename from util-linux, will do that for you. It replaces every occurrence of "txt" with "text" in all files matching "*.txt":

rename txt text *.txt
5
  • 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, 2011 at 21:28
  • 21
    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, 2013 at 15:12
  • 1
    @evilsoup That's the case for OSX, thx
    – Davi Lima
    Aug 17, 2015 at 19:23
  • This also works on Cygwin (Windows)
    – andy
    Apr 12, 2017 at 9:36
  • Replace rename with rename.ul on Ubuntu/Debian.
    – Quandary
    Jun 2 at 12:57
55
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, 2015 at 8:57
  • Still, it is a useful option since "*.txt" can unroll to a large argument list unsupported by the shell. Oct 19, 2016 at 13:14
  • 3
    "s/oldExtension/newExtension/" will fail for footxt.txt. Use this instead: unix.stackexchange.com/questions/19654/…
    – wisbucky
    Aug 1, 2017 at 23:59
35

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
22

Reason #53 to switch to zsh:

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

3
  • 5
    For Mac, I have to run this command "autoload -U zmv". Otherwise, I am getting "zsh: command not found: zmv" Oct 7, 2021 at 15:35
  • I'm guessing autoload -U zmv means, on "my user profile" autoload zmv. Thanks a bunch for this =) Nov 16, 2021 at 5:05
  • Running zsh 5.8 (x86_64-ubuntu-linux-gnu). No zmv command. is it supposed to be installed by default?
    – Olsgaard
    Jun 1 at 4:59
13

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

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
10

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.

7

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
2
  • 1
    Excellent bash-native script. worked great! for f in *.html; do mv -- "$f" "${f%.\*}.pug"; done Jul 19, 2019 at 17:54
  • This does not change the extension. It adds to it. You have to use basename to extract the base part of the filename.
    – crafter
    Feb 7 at 7:59
4

This is what works for me:

find . -name '*.txt' -exec rename 's/\.txt$/.text/' \{} \;
0
4

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

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' 
1
0

Nobody has shown using shell parameter expansion, which is the most basic, probably shell compliant, (and I believe most readable) way of doing this.

# by removing suffix 
for f in *.txt; do echo "$f" "${f%txt}text"; done

# by substitution
for f in *.txt; do echo "$f" "${f/%txt/text}"; done

# simply edit mv for echo after testing
# % searched for shortest string, %% is 'greedy' would search for longest
# if you have a basename, use basename*.txt

https://www.debuntu.org/how-to-bash-parameter-expansion-and-string-manipulation/
https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html

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