How do I change extension of multiple files recursively from the command line?
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
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/'
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 only match filenames that end in .abc -
'{}'
is the placeholder that marks the place where-execdir
will insert the found path. The single-quotes are required, to allow it to handle file names with spaces and shell characters. - 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 if you put your command all on one line. - However, the backslash at the end of the
-execdir
line is required. It is there to escape 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 (note: this part vary depending on your version offind
. See comment from user @apollo) -
(.+)
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" partversion1_
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.
Another portable way:
find /the/path -depth -type f -name "*.abc" -exec sh -c 'mv -- "$1" "$(dirname "$1")/$(basename "$1" .abc).edefg"' _ '{}' \;
# 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