How do I rename the extension for a bunch of files?
Asked Answered
Z

28

559

In a directory, I have a bunch of *.html files. I'd like to rename them all to *.txt

How can I do that?
I use the Bash shell.

Zosi answered 3/8, 2009 at 21:42 Comment(0)
H
417

For an better solution (with only bash functionality, as opposed to external calls), see one of the other answers.


The following would do and does not require the system to have the rename program (although you would most often have this on a system):

for file in *.html; do
    mv "$file" "$(basename "$file" .html).txt"
done

EDIT: As pointed out in the comments, this does not work for filenames with spaces in them without proper quoting (now added above). When working purely on your own files that you know do not have spaces in the filenames this will work but whenever you write something that may be reused at a later time, do not skip proper quoting.

Housen answered 3/8, 2009 at 21:46 Comment(11)
An alternative, without basename & with quotes: mv "${file}" "${file/%.html/.txt}" (see man bash, Parameter Expansion for details)Albania
Only good if the files are all in the current directory, of course, because basename strips off the pathname part. Just a 'beware'!Barone
if there are many html files, use bash's internal string functions instead of basename.Jounce
+1 for use of basename; although the question specifies it is for the bash shell, portability is good!Scrotum
This solution is bad, not only because it is slow but because it does not work with filenames with spaces in them. You should ALWAYS do proper quotation in bash scripts. mv "$file" "$(basename "$file" .html)".txt would be much better. But still, mv "$files" "${files%.html}.txt" is much better.Arlenaarlene
I agree with Pozsar. If you are not careful, this can really make for a bad day. Just a warning.Polyandrous
in windows you just do ren *.a *.bMcdavid
all my files (which has a space in them) vanished except for the last one file which remains as <empt_name>.txt :(Vanegas
At minimum, use POSIX-specified $() instead of legacy backtick syntax. Improves readability, and makes syntax much less ambiguous when you have characters that would need to be backslash-escaped to be literal inside the command substitution with the latter.Cressler
@MuhammadUmer Thanks! Worked! (Just when thought you couldn't do batch commands in windows...)Mucus
For what it's worth, some of the earlier comments are no longer relevant; the quoting errors in this answer were fixed in 2016 so it should be reasonably safe in its current form.Middlebreaker
J
772

If using Bash, there's no need for external commands like sed, basename, rename, expr etc.

for file in *.html
do
  mv "$file" "${file%.html}.txt"
done
Jounce answered 4/8, 2009 at 0:17 Comment(8)
And if you don't know the file extension you can use "${file%.*}.txt", but this could be dangerous for files w/o an extension at all.Classicism
Note to anyone having trouble getting this working like I had: notice that there is no $ inside the curly braces!Potage
I need a way to permanently favorite/bookmark this answer, I never remember the exact syntax and I end up googling for itPrehistoric
Is there a way to make this work recursively, i.e. even if the files to rename are not directly located in the current working dir but in its various direct and indirect child directories?Hamlet
I'm not sure how this is working? Can you explain the logic of the mv command?Joappa
If you google "linux rename multiple extension" you don't find this answer, you find some unsatisfying answers on unix.stackexchange and end up spending some time figuring out the correct one because you know how mv file{1,2} works leading to this answer. Googling "linux batch rename" would have led me here directly...Nonviolence
@danip The percent-sign-within-bracket construct strips characters off the end. There is also a hash-within-bracket construct that strips characters off the beginning. Check it: tldp.org/LDP/abs/html/parameter-substitution.html#PSUB2Percolator
Another Stack exchange answer (that I can't find!) suggested this, but also using the -- "operator": mv -- "$file" "${file%.html}.txt" That operator prevents file names that start with a '-' from being parsed by mv as arguments.Tew
H
417

For an better solution (with only bash functionality, as opposed to external calls), see one of the other answers.


The following would do and does not require the system to have the rename program (although you would most often have this on a system):

for file in *.html; do
    mv "$file" "$(basename "$file" .html).txt"
done

EDIT: As pointed out in the comments, this does not work for filenames with spaces in them without proper quoting (now added above). When working purely on your own files that you know do not have spaces in the filenames this will work but whenever you write something that may be reused at a later time, do not skip proper quoting.

Housen answered 3/8, 2009 at 21:46 Comment(11)
An alternative, without basename & with quotes: mv "${file}" "${file/%.html/.txt}" (see man bash, Parameter Expansion for details)Albania
Only good if the files are all in the current directory, of course, because basename strips off the pathname part. Just a 'beware'!Barone
if there are many html files, use bash's internal string functions instead of basename.Jounce
+1 for use of basename; although the question specifies it is for the bash shell, portability is good!Scrotum
This solution is bad, not only because it is slow but because it does not work with filenames with spaces in them. You should ALWAYS do proper quotation in bash scripts. mv "$file" "$(basename "$file" .html)".txt would be much better. But still, mv "$files" "${files%.html}.txt" is much better.Arlenaarlene
I agree with Pozsar. If you are not careful, this can really make for a bad day. Just a warning.Polyandrous
in windows you just do ren *.a *.bMcdavid
all my files (which has a space in them) vanished except for the last one file which remains as <empt_name>.txt :(Vanegas
At minimum, use POSIX-specified $() instead of legacy backtick syntax. Improves readability, and makes syntax much less ambiguous when you have characters that would need to be backslash-escaped to be literal inside the command substitution with the latter.Cressler
@MuhammadUmer Thanks! Worked! (Just when thought you couldn't do batch commands in windows...)Mucus
For what it's worth, some of the earlier comments are no longer relevant; the quoting errors in this answer were fixed in 2016 so it should be reasonably safe in its current form.Middlebreaker
B
206
rename 's/\.html$/\.txt/' *.html

does exactly what you want.

Besom answered 3/8, 2009 at 21:45 Comment(5)
I don't think you can use a literal regex in bash like you suggest - which shell are you using?Christo
Here's the man page for the version of rename on Ubuntu: unixhelp.ed.ac.uk/CGI/man-cgi?renameBesom
rename is a command on some systems. I have a Perl script (originally from the first Camel book) that does the job. There's also a GNU program of the same name that does roughly the same job. My Mac doesn't have a system-provided 'rename' command - or it isn't on my PATH (which is moderately comprehensive).Barone
There is a rename formula in Homebrew.Demp
I like your answer. But in fact i will just use rename 's/jpg/png/' *.jpg, this is easier to remember and type. It may cause error if there is a filename contains jpg, so I will check it first before typing.Cornel
R
153

This worked for me on OSX from .txt to .txt_bak

find . -name '*.txt' -exec sh -c 'mv "$0" "${0%.txt}.txt_bak"' {} \;
Randi answered 4/12, 2014 at 3:12 Comment(6)
Works fine in linux too.Battleplane
It's besides the point, but to go from .txt to .txt_bak you just have to concatenate _bak ;)Dissertation
This is nice for renaming recursivelyStickler
Great! (under Ubuntu 16.04) My practical use case, renaming all .scss to .sass (after in-place conversion…): find . -name '*.scss' -exec sh -c 'mv "$0" "${0%.scss}.sass"' {} \;Tartuffery
this worked with +6000 files, rename reported "argument list was too long"Fortunato
I used it in windows in GIT hooks (which means the shell (Linux))- it works nice. I wanted to remove the .tmp extension, so that I used it a little bit modified: find . -name '*.txt' -exec sh -c 'mv "$0" "${0%.tmp}"' {} \; (in case somebody wants to rename the extension with nothing (remove the .tmp extension of the files) Thnx @StevenArdatharde
C
103

You want to use rename :

rename -S <old_extension> <new_extension> <files>

rename -S .html .txt *.html

This does exactly what you want - it will change the extension from .html to .txt for all files matching *.html.

Note: Greg Hewgill correctly points out this is not a bash builtin; and is a separate Linux command. If you just need something on Linux this should work fine; if you need something more cross-platform then take a look at one of the other answers.

Christo answered 3/8, 2009 at 21:44 Comment(11)
Although this is a good solution, the rename program is not related to bash and is also not available on all platforms. I've only seen it on Linux.Omen
"$rename .html .txt *.html" results in... syntax error at (eval 1) line 1, near "."Besom
@Greg: Ah yes you're right - I'd always assumed it was a bash builtin. However I don't think I've ever come across a Linux system which didn't have it; so if you're only need this for Linux rename is probably the simplest method.Christo
Correct syntax is rename -S .html .text *.html where -S stands for --subst-allMancuso
@GregHewgill rename is available for Mac OS with HomeBrewTranslucid
I updated the answer to add the missing -S switch as per @MarekSebera correction.Linguini
There is no -S option for rename 2.28.2 of util-linux. It works without it, though.Teal
My Ubuntu 16 doesn't include -S for rename.Cy
Unknown option: S on ubuntuCornel
On Mac with Homebrew rename version use lowercase s for substitute command: rename -s .html .txt *.htmlCavour
Just to recap what several of the comments here are implying, but few are saying; there are multiple rename tools with different options and different syntax, even if you have exactly the same base Linux (or FreeBSD etc) system as somebody else. What works depends on which rename you have, if you have one at all. This is all nonstandard, so YMMV.Middlebreaker
C
36

On a Mac...

  1. Install rename if you haven't: brew install rename
  2. rename -S .html .txt *.html
Certie answered 22/7, 2015 at 22:28 Comment(0)
B
16

For Ubuntu Users :

rename 's/\.html$/\.txt/' *.html
Benkley answered 27/2, 2015 at 8:0 Comment(1)
This isn't recursive, but it worked for me on Ubuntu 14.04.Sou
S
16

This is the slickest solution I've found that works on OSX and Linux, and it works nicely with git too!

find . -name "*.js" -exec bash -c 'mv "$1" "${1%.js}".tsx' - '{}' \;

and with git:

find . -name "*.js" -exec bash -c 'git mv "$1" "${1%.js}".tsx' - '{}' \;

Splenetic answered 21/9, 2018 at 13:46 Comment(3)
Not really slick, since it starts a new bash for every single file. Quite slow, actually. Use pipe and xargs, please.Putrescent
I suppose that's true. I can't imagine many cases where you're naming so many files that performance would matter. This method can rename hundreds within seconds. So yeah, I guess maybe it's not great performance, but it's a slick solution if you don't...Splenetic
There is another limitation here compared to the xargs solution: You can't easily parallelize it. With xargs you can simply add -P 10 and it will fork you command with up to 10 concurrent processes. Really good for handling large directories. Use of ls -1 was just an example of how to feed xargs - you could use it with find tooJahdiel
A
10

This question explicitly mentions Bash, but if you happen to have ZSH available it is pretty simple:

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

If you get zsh: command not found: zmv then simply run:

autoload -U zmv

And then try again.

Thanks to this original article for the tip about zmv.

Adenectomy answered 20/6, 2017 at 2:45 Comment(1)
zmv '(*).html' '$1.txt' to use the specific file extensions from the original question.Sacred
S
9

In Linux or window git bash or window's wsl, try below command to change every file's extension in current directory or sub-directories or even their sub-directories with just one line of code

find . -depth -name "*.html" -exec sh -c 'mv "$1" "${1%.html}.txt"' _ {} \;
Sadomasochism answered 1/9, 2021 at 5:57 Comment(1)
worked for me in windows 11 git bash (MINGW64).Paripinnate
P
8

Here is an example of the rename command:

rename -n ’s/\.htm$/\.html/’ *.htm

The -n means that it's a test run and will not actually change any files. It will show you a list of files that would be renamed if you removed the -n. In the case above, it will convert all files in the current directory from a file extension of .htm to .html.

If the output of the above test run looked ok then you could run the final version:

rename -v ’s/\.htm$/\.html/’ *.htm

The -v is optional, but it's a good idea to include it because it is the only record you will have of changes that were made by the rename command as shown in the sample output below:

$ rename -v 's/\.htm$/\.html/' *.htm
3.htm renamed as 3.html
4.htm renamed as 4.html
5.htm renamed as 5.html

The tricky part in the middle is a Perl substitution with regular expressions, highlighted below:

rename -v ’s/\.htm$/\.html/’ *.htm
Platonism answered 28/9, 2014 at 5:19 Comment(0)
J
7

One line, no loops:

ls -1 | xargs -L 1 -I {} bash -c 'mv $1 "${1%.*}.txt"' _ {}

Example:

$ ls
60acbc4d-3a75-4090-85ad-b7d027df8145.json  ac8453e2-0d82-4d43-b80e-205edb754700.json
$ ls -1 | xargs -L 1 -I {} bash -c 'mv $1 "${1%.*}.txt"' _ {}
$ ls
60acbc4d-3a75-4090-85ad-b7d027df8145.txt  ac8453e2-0d82-4d43-b80e-205edb754700.txt
Jahdiel answered 13/5, 2019 at 20:55 Comment(3)
+1 for showing a bash-only way which does not depend on matching the existing extension exactly. This works for any input extension, be it 3 chars long or 4 chars long or moreCathe
Improvement: $ ls -1 | xargs -L 1 -I {} bash -c 'mv "$1" "${1%.*}.swift"' _ {}Zhao
You don't need ls or xargs; for f in ./*; do mv "$f" "${f%.*}.swift; doneMiddlebreaker
S
5

The command mmv seems to do this task very efficiently on a huge number of files (tens of thousands in a second). For example, to rename all .xml files to .html files, use this:

mmv ";*.xml" "#1#2.html"

the ; will match the path, the * will match the filename, and these are referred to as #1 and #2 in the replacement name.

Answers based on exec or pipes were either too slow or failed on a very large number of files.

Sophiasophie answered 3/11, 2017 at 14:0 Comment(1)
This is obscure and nonstandard; it might be the same as github.com/rrthomas/mmv but the man page you link to reveals nothing about its provenance or availability.Middlebreaker
L
4

Try this

rename .html .txt *.html 

usage:

rename [find] [replace_with] [criteria]
Longboat answered 6/2, 2014 at 11:32 Comment(0)
N
4

After someone else's website crawl, I ended up with thousands of files missing the .html extension, across a wide tree of subdirectories.

To rename them all in one shot, except the files already having a .html extension (most of them had none at all), this worked for me:

cd wwwroot
find . -xtype f \! -iname *.html   -exec mv -iv "{}"  "{}.html"  \;  # batch rename files to append .html suffix IF MISSING

In the OP's case I might modify that slightly, to only rename *.txt files, like so:

find . -xtype f  -iname *.txt   -exec filename="{}"  mv -iv ${filename%.*}.{txt,html}  \; 

Broken down (hammertime!):

-iname *.txt
- Means consider ONLY files already ending in .txt

mv -iv "{}.{txt,html}" - When find passes a {} as the filename, ${filename%.*} extracts its basename without any extension to form the parameters to mv. bash takes the {txt,html} to rewrite it as two parameters so the final command runs as: mv -iv "filename.txt" "filename.html"

Fix needed though: dealing with spaces in filenames

Nanananak answered 3/7, 2014 at 11:51 Comment(0)
M
4

This is a good way to modify multiple extensions at once:

for fname in *.{mp4,avi}
do
   mv -v "$fname" "${fname%.???}.mkv"
done

Note: be careful at the extension size to be the same (the ???)

Mahon answered 22/9, 2018 at 20:38 Comment(0)
C
4

Rename file extensions for all files under current directory and sub directories without any other packages (only use shell script):

  1. Create a shell script rename.sh under current directory with the following code:

    #!/bin/bash
    
    for file in $(find . -name "*$1"); do
      mv "$file" "${file%$1}$2"
    done
    
  2. Run it by ./rename.sh .old .new.

    Eg. ./rename.sh .html .txt

Collinear answered 3/12, 2019 at 23:23 Comment(2)
This suffers from the same problem as the find solution above: You can't parallelize it as easily. Sure, you can add & to your mv, but then you could have 100s or 1000s of spawned processes. Using xargs you can assure yourself that -P 20 will allow a max of 20 forked processes.Jahdiel
for file in $(find ...) is inherently broken for file names which contain whitespace or shell metacharacters. See mywiki.wooledge.org/BashFAQ/020Middlebreaker
G
3

A bit late to the party. You could do it with xargs:

ls *.html | xargs -I {} sh -c 'mv $1 `basename $1 .html`.txt' - {}

Or if all your files are in some folder

ls folder/*.html | xargs -I {} sh -c 'mv $1 folder/`basename $1 .html`.txt' - {}
Gyatt answered 17/6, 2015 at 10:9 Comment(2)
No. Don't parse ls. This command is ridiculous: it uselessly uses a glob with ls, instead of directly using the glob. This will break with filenames containing spaces, quotes and (due to the lack of quotes) glob characters.Netsuke
FYI, your linked article contains an updated note that says newer LS 'correctly "shell escapes" files if printed to the terminal.' Your point is still a good rule of thumb though.Cy
B
3

Similarly to what was suggested before, this is how I did it:

find . -name '*OldText*' -exec sh -c 'mv "$0" "${0/OldText/NewText}"' {} \;

I first validated with

find . -name '*OldText*' -exec sh -c 'echo mv "$0" "${0/OldText/NewText}"' {} \;
Blurt answered 6/12, 2019 at 8:41 Comment(0)
D
3

The easiest way is to use rename.ul it is present in most of the Linux distro

rename.ul -o -v [oldFileExtension] [newFileExtension] [expression to search for file to be applied with]

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

Options:

-o: don't overwrite preexisting .newext

-v: verbose

-n: dry run

Disquieting answered 10/7, 2021 at 5:44 Comment(1)
This surprised me and TIL that this exists in Ubuntu 20.04 out of the box. ThanksConfucius
S
2

If you prefer Perl, there is a short Perl script (originally written by Larry Wall, the creator of Perl) that will do exactly what you want here: tips.webdesign10.com/files/rename.pl.txt.

For your example the following should do the trick:

rename.pl 's/html/txt/' *.html
Slover answered 28/6, 2013 at 12:13 Comment(2)
This question has already been answered and accepted a long time ago and it doesn't seem that your answer bring anything more than what has already been said.Terrapin
+1 since it was a Larry Wall script (modified by Robin Barker). The last available url is this: tips.webdesign10.com/files/rename.pl.txtRothermere
L
1

Unfortunately it's not trivial to do portably. You probably need a bit of expr magic.

for file in *.html; do echo mv -- "$file" "$(expr "$file" : '\(.*\)\.html').txt"; done

Remove the echo once you're happy it does what you want.

Edit: basename is probably a little more readable for this particular case, although expr is more flexible in general.

Lasley answered 3/8, 2009 at 21:45 Comment(1)
While this may not be the best answer for the question, it was for me! I needed a way to rename only in string a whole path, not just a the file name. Thanks for posting!Disseminate
W
1

Here is what i used to rename .edge files to .blade.php

for file in *.edge; do     mv "$file" "$(basename "$file" .edge).blade.php"; done

Works like charm.

Welldressed answered 7/2, 2018 at 8:23 Comment(0)
E
1

Nice & simple!

find . -iname *.html  -exec mv {} "$(basename {} .html).text"  \;
Equilibrate answered 30/4, 2019 at 20:56 Comment(1)
"$(basename {} .html).text" gets expanded by the shell to {}.text before find runs; this is broken.Middlebreaker
D
1

You can also make a function in Bash, add it to .bashrc or something and then use it wherever you want.

change-ext() {
    for file in *.$1; do mv "$file" "$(basename "$file" .$1).$2"; done
}

Usage:

change-ext css scss

Source of code in function: https://mcmap.net/q/73358/-how-do-i-rename-the-extension-for-a-bunch-of-files

Donniedonnish answered 15/8, 2019 at 9:43 Comment(2)
If you get the error 'Bad substitution', then the 'sh' command probably doesn't point to bash. Replace sh with /bin/bash and it should work.Chile
@WesleyDeKeirsmaeker there is nothing here which is specific to Bash, or would produce a "bad substitution" error (though ."$1" should have quotes around it in both places).Middlebreaker
M
0

Here is a solution, using AWK. Make sure the files are present in the working directory. Else, cd to the directory where the html files are located and then execute the below command:

for i in $(ls | grep .html); do j=$(echo $i | grep -oh "^\w*." | awk '{print $1"txt"}'); mv $i $j; done
Melson answered 28/7, 2020 at 12:12 Comment(1)
This has multiple problems for robustness and portability; probably run it through shellcheck.net for full diagnostics. In brief, don't use ls in scripts and quote your variables.Middlebreaker
T
0

I wrote this code in my .bashrc

alias find-ext='read -p "Path (dot for current): " p_path; read -p "Ext (no punctuation): " p_ext1; find $p_path -type f -name "*."$p_ext1'
alias rename-ext='read -p "Path (dot for current): " p_path; read -p "Ext (no punctuation): " p_ext1; read -p "Change by ext. (no punctuation): " p_ext2; echo -en "\nFound files:\n"; find $p_path -type f -name "*.$p_ext1"; find $p_path -type f -name "*.$p_ext1" -exec sh -c '\''mv "$1" "${1%.'\''$p_ext1'\''}.'\''$p_ext2'\''" '\'' _ {} \;; echo -en "\nChanged Files:\n"; find $p_path -type f -name "*.$p_ext2";'

In a folder like "/home/<user>/example-files" having this structure:

  • /home/<user>/example-files:
    • file1.txt
    • file2.txt
    • file3.pdf
    • file4.csv

The commands would behave like this:

~$ find-text
Path (dot for current): example-files/
Ext (no punctuation): txt

example-files/file1.txt
example-files/file2.txt


~$ rename-text
Path (dot for current): ./example-files
Ext (no punctuation): txt
Change by ext. (no punctuation): mp3

Found files:
./example-files/file1.txt
./example-files/file1.txt

Changed Files:
./example-files/file1.mp3
./example-files/file1.mp3
~$
Tellurion answered 21/1, 2021 at 21:44 Comment(3)
"Punctured" would mean "having a small undesired hole"; I guess you mean without punctuation?Middlebreaker
Yes, you are right!Tellurion
Probably prefer functions over aliases.Middlebreaker
H
0

You could use a tool designed for renaming files in bulk, e.g. renamer.

To rename all file extensions in the current folder:

$ renamer --find ".html" --replace ".txt" --dry-run * 

Many more usage examples here.

Hyoscyamine answered 1/5, 2021 at 12:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.