69

I have many files with .abc extension and want to change them to .edefg
How to do this from command line ?

I have a root folder with many sub-folders, so the solution should work recursively.

81

A portable way (which will work on any POSIX compliant system):

find /the/path -depth -name "*.abc" -exec sh -c 'mv "$1" "${1%.abc}.edefg"' _ {} \;

In bash4, you can use globstar to get recursive globs (**):

shopt -s globstar
for file in /the/path/**/*.abc; do
  mv "$file" "${file%.abc}.edefg"
done

The (perl) rename command in Ubuntu can rename files using perl regular expression syntax, which you can combine with globstar or find:

# Using globstar
shopt -s globstar
files=(/the/path/**/*.abc)  

# Best to process the files in chunks to avoid exceeding the maximum argument 
# length. 100 at a time is probably good enough. 
# See http://mywiki.wooledge.org/BashFAQ/095
for ((i = 0; i < ${#files[@]}; i += 100)); do
  rename 's/\.abc$/.edefg/' "${files[@]:i:100}"
done

# Using find:
find /the/path -depth -name "*.abc" -exec rename 's/\.abc$/.edefg/' {} +

Also see http://mywiki.wooledge.org/BashFAQ/030

  • the first one simply appends the new extension onto the old one, it doesn't replace... – user2757729 May 21 '14 at 15:35
  • 3
    @user2757729, it does replace it. "${1%.abc}" expands to the value of $1 except without .abc at the end. See faq 100 for more on shell string manipulations. – geirha May 22 '14 at 8:51
  • I ran this on a ~70k files file structure... at least on my shell it most definitely did not replace it. – user2757729 May 22 '14 at 15:52
  • Sorry just set up a test dir structure and it seems to be working. Wonder what I did wrong yesterday. – user2757729 May 22 '14 at 15:56
  • 1
    @user2757729 You probably left the "abc" in ${1%.abc}... – kvnn May 3 '16 at 21:47
43

This will do the required task if all the files are in the same folder

rename 's/.abc$/.edefg/' *.abc

To rename the files recursively use this:

find /path/to/root/folder -type f -name '*.abc' -print0 | xargs -0 rename 's/.abc$/.edefg/'
  • 7
    Or rename 's/.abc$/.edefg/' /path/to/root/folder/**/*.abc in a modern version of Bash. – Adam Byrtek Apr 19 '11 at 18:35
  • Great thanks Adam for giving me a tip on how to use *.abc in folders recursively! – Rafał Cieślak Apr 19 '11 at 18:42
  • Great tip, thanks ! Where can I find more documentation about the little piece of regex's syntax ? Like what's the s at the beginning, and what other options can I use in there. Thanks ! – Anto Mar 8 '13 at 1:23
  • @AdamByrtek I just failed with your suggestion on Utopic. ('no such files' or some such.) – Jonathan Y. Apr 30 '15 at 8:25
13

One problem with recursive renames is that whatever method you use to locate the files, it passes the whole path to rename, not just the file name. That makes it hard to do complex renames in nested folders.

I use find's -execdir action to solve this problem. If you use -execdir instead of -exec, the specified command is run from the subdirectory containing the matched file. So, instead of passing the whole path to rename, it only passes ./filename. That makes it much easier to write the regex.

find /the/path -type f \
               -name '*.abc' \
               -execdir rename 's/\.\/(.+)\.abc$/version1_$1.abc/' '{}' \;

In detail:

  • -type f means only look for files, not directories
  • -name '*.abc' means means only match filenames that end in .abc
  • The backslashes after -type and -name are the bash line-continuation character. I use them to make this example more readable, but they are not needed in practice.
  • However, the backslash at the end of the -execdir line is required. It is there to esacpe the semicolon, which terminates the command run by -execdir. Fun!

Explanation of the regex:

  • s/ start of the regex
  • \.\/ match the leading ./ that -execdir passes in. Use \ to escape the . and / metacharacters
  • (.+) match the filename. The parentheses capture the match for later use
  • \.abc escape the dot, match the abc
  • $ anchor the match at the end of the string

  • / marks the end of the "match" part of the regex, and the start of the "replace" part

  • version1_ add this text to every file name

  • $1 references the existing filename, because we captured it with parentheses. If you use multiple sets of parentheses in the "match" part, you can refer to them here using $2, $3, etc.
  • .abc the new file name will end in .abc. No need to escape the dot metacharacter here in the "replace" section
  • / end of the regex

Before

tree --charset=ascii

|-- a_file.abc
|-- Another.abc
|-- Not_this.def
`-- dir1
    `-- nested_file.abc

After

tree --charset=ascii

|-- version1_a_file.abc
|-- version1_Another.abc
|-- Not_this.def
`-- dir1
    `-- version1_nested_file.abc

Hint: rename's -n option is useful. It does a dry run and shows you what names it will change, but does not make any changes.

5

Another portable way:

find /the/path -depth -type f -name "*.abc" -exec sh -c 'mv -- "$1" "$(dirname "$1")/$(basename "$1" .abc).edefg"' _ '{}' \;
  • 4
    Welcome to Ask Ubuntu! While this is a valuable answer, I recommend expanding it (by editing) to explain how and why that command works. – Eliah Kagan Feb 3 '13 at 16:44
1
# 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

https://unix.stackexchange.com/questions/19654/changing-extension-to-multiple-files

0

This is what I did and worked pretty just the way I wanted. I used the mv command. I had multiple .3gp files and I wanted to rename them all to .mp4

Here's a short oneliner for it:

for i in *.3gp; do mv -- "$i" "ren-$i.mp4"; done

Which simply scans through the current directory, picks up all .3gp files, then renames (using the mv) into ren-name_of_file.mp4

  • That will rename file.3gp to file.3gp.mp4, which might not be what most people want. – muru Mar 13 '16 at 22:05
  • @muru but the principle still exist. Simply select any extension then specify the destination extension to get them renamed. The .3gp or .mp4 here were just for illustration purposes. – Rexford Mar 15 '16 at 13:56
  • The syntax is preferrable, one-liner and easy to understand. However, I would use basename "$i" .mp4 to remove the previous extension instead of "ren-$i.mp4". – TaoPR May 13 '16 at 8:13
  • True. Good point. – Rexford May 13 '16 at 8:21
0

I found an easy way to achieve this. To change extensions of many files from jpg to pdf, use:

for file in /path/to; do mv $file $(basename -s jpg $file)pdf ; done
0

I'd use the mmv command from the package of the same name:

mmv ';*.abc' '#1#2.edefg'

The ; matches zero or more */ and corresponds to #1 in the replacement. The * corresponds to #2. The non-recursive version would be

mmv '*.abc' '#1.edefg'
0

Rename files and directories with find -execdir | rename

If you are going to rename both files and directories not simply with a suffix, then this is a good pattern:

PATH="$(echo "$PATH" | sed -E 's/(^|:)[^\/][^:]*//g')" \
  find . -depth -execdir rename 's/findme/replaceme/' '{}' \;

The awesome -execdir option does a cd into the directory before executing the rename command, unlike -exec.

-depth ensure that the renaming happens first on children, and then on parents, to prevent potential problems with missing parent directories.

-execdir is required because rename does not play well with non-basename input paths, e.g. the following fails:

rename 's/findme/replaceme/g' acc/acc

The PATH hacking is required because -execdir has one very annoying drawback: find is extremely opinionated and refuses to do anything with -execdir if you have any relative paths in your PATH environment variable, e.g. ./node_modules/.bin, failing with:

find: The relative path ‘./node_modules/.bin’ is included in the PATH environment variable, which is insecure in combination with the -execdir action of find. Please remove that entry from $PATH

See also: Why using the '-execdir' action is insecure for directory which is in the PATH?

-execdir is a GNU find extension to POSIX. rename is Perl based and comes from the rename package. Tested in Ubuntu 18.10.

Rename lookahead workaround

If your input paths don't come from find, or if you've had enough of the relative path annoyance, we can use some Perl lookahead to safely rename directories as in:

git ls-files | sort -r | xargs rename 's/findme(?!.*\/)\/?$/replaceme/g' '{}'

I haven't found a convenient analogue for -execdir with xargs: https://superuser.com/questions/893890/xargs-change-working-directory-to-file-path-before-executing/915686

The sort -r is required to ensure that files come after their respective directories, since longer paths come after shorter ones with the same prefix.

protected by Community Jun 7 '17 at 12:00

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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