123

I want to recursively iterate through a directory and change the extension of all files of a certain extension, say .t1 to .t2. What is the bash command for doing this?

anubhava
  • 761,203
  • 64
  • 569
  • 643
Amal Antony
  • 6,477
  • 14
  • 53
  • 76

6 Answers6

245

Use:

find . -name "*.t1" -exec bash -c 'mv "$1" "${1%.t1}".t2' - '{}' +

If you have rename available then use one of these:

find . -name '*.t1' -exec rename .t1 .t2 {} +
find . -name "*.t1" -exec rename 's/\.t1$/.t2/' '{}' +
Slava Fomin II
  • 26,865
  • 29
  • 124
  • 202
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • 20
    `find . -name '*.t1' -exec rename .t1 .t2 {} +` – Aaron Blenkush Dec 09 '15 at 00:00
  • 2
    (My version of `rename` doesn't allow the sed style substitution expression. Gotta love Linux. I used to have to install TotalCommander for Windows to do stuff like this.) – Aaron Blenkush Dec 09 '15 at 00:03
  • 1
    Please use a parameter expansion instead of an ugly `sed`! – gniourf_gniourf Sep 10 '16 at 11:01
  • 1
    If you have the extensions in variables, you cannot use single quotes here. `find . -name "*.$ext1" -exec bash -c "mv \"\$1\" \"\$(sed 's/\\.$ext1\$/.$ext2/' <<< \"\$1\")\"" - {} \;` should work instead, though this is much more involved than I would like it to be. – tripleee Sep 11 '16 at 13:58
  • 7
    In case anyone is wondering what the `"${1%.t1}".t2` part does, like I did: It uses bash string manipulation to do the following: 1/ Take the first positional parameter `$1` and truncate the `.t1` string literal from its end (percentage sign `%` operator). 2/ Append the `.t2` string literal to the result. – Zack Jul 21 '17 at 01:55
  • 2
    The rename didn't work for me in OSX, but the bash version is awesome b/c I just added 'git' in front of mv and now git is happy :-D – bdombro Sep 21 '18 at 13:37
  • 5
    prefer to user `find . -type f -name '*.t1'` to avoid folders – Golak Sarangi Jan 15 '19 at 11:44
  • 1
    works like a charm! Install "rename" by `sudo apt install rename` – Nikhil VJ Mar 11 '20 at 09:46
  • 1
    Another form would be `mv "$1" "${1/.t1/.t2}"` this uses find and replace, but your filename need to have only one ".t1". – axell-brendow May 28 '20 at 14:33
  • 2
    @AaronBlenkush In Windows I would use PowerShell for this: `gci -r *.t1 | foreach { $_ -replace ".t1", ".t2" }` these commands will print the filenames renamed. – axell-brendow May 28 '20 at 14:40
  • 2
    On MacOS it only renames the first matched file – chill appreciator Nov 24 '21 at 13:36
  • 14
    Delimiter argument should be `;` instead of `+` if renaming all at once is required like this `find . -name "*.t1" -exec bash -c 'mv "$1" "${1%.t1}".t2' - '{}' \;`. Otherwise with the `+` only one file will be renamed at a time. [Ref](https://www.baeldung.com/linux/find-exec-command) – S.aad Jan 20 '22 at 14:09
48

None of the suggested solutions worked for me on a fresh install of debian 11. This should work on any Posix/MacOS

find ./ -depth -name "*.t1" -exec sh -c 'mv "$1" "${1%.t1}.t2"' _ {} \;

All credits to: https://askubuntu.com/questions/35922/how-do-i-change-extension-of-multiple-files-recursively-from-the-command-line

Paul Oskar Mayer
  • 1,107
  • 1
  • 10
  • 22
17

If your version of bash supports the globstar option (version 4 or later):

shopt -s globstar
for f in **/*.t1; do
    mv "$f" "${f%.t1}.t2"
done 
chepner
  • 497,756
  • 71
  • 530
  • 681
10

I would do this way in bash :

for i in $(ls *.t1); 
do
    mv "$i" "${i%.t1}.t2" 
done

EDIT : my mistake : it's not recursive, here is my way for recursive changing filename :

for i in $(find `pwd` -name "*.t1"); 
do 
    mv "$i" "${i%.t1}.t2"
done
jrjc
  • 21,103
  • 9
  • 64
  • 78
  • 10
    [Don't parse ls](http://mywiki.wooledge.org/ParsingLs), and see the same page for why your `find` syntax is bad. Also, make sure you [quote your variables](http://mywiki.wooledge.org/BashGuide/Practices#Quoting) – Reinstate Monica Please Feb 24 '14 at 12:26
5

Or you can simply install the mmv command and do:

mmv '*.t1' '#1.t2'

Here #1 is the first glob part i.e. the * in *.t1 .

Or in pure bash stuff, a simple way would be:

for f in *.t1; do
    mv "$f" "${f%.t1}.t2"
done

(i.e.: for can list files without the help of an external command such as ls or find)

HTH

Dave Yarwood
  • 2,866
  • 1
  • 17
  • 29
zmo
  • 24,463
  • 4
  • 54
  • 90
1

My lazy copy-pasting of one of these solutions didn't work, but I already had fd-find installed, so I used that:

fd --extension t1 --exec mv {} {.}.t2

From fd's manpage, when executing a command (using --exec):

          The following placeholders are substituted by a
          path derived from the current search result:

          {}     path
          {/}    basename
          {//}   parent directory
          {.}    path without file extension
          {/.}   basename without file extension
Richard Turner
  • 12,506
  • 6
  • 36
  • 37