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.
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.
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/' {} +
"${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 08:51
bash
instead of sh
in the first command?
– pqnet
Jun 19 '15 at 14:59
${1%.abc}
is defined by POSIX, if that's what you're referring to.
– geirha
Jun 19 '15 at 19:07
sh -c
the first argument is assigned to $0 and any remaining arguments are assigned to the positional parameters. So the _
is a dummy argument, and '{}' is the first positional argument to sh -c
.
– spaceghost
Apr 09 '19 at 12:26
find -iname "*.img" -print0 | xargs -L 1 -I {} -0 sh -c 'mv "{}" "${{}%.img}.iso"'
– Ray Foss
Sep 24 '19 at 13:59
...|xargs ... -0 sh -c 'mv "$1" "${1%.img}.iso"' sh {}
– geirha
Sep 26 '19 at 07:31
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/'
rename 's/.abc$/.edefg/' /path/to/root/folder/**/*.abc
in a modern version of Bash.
– Adam Byrtek
Apr 19 '11 at 18:35
s
at the beginning, and what other options can I use in there. Thanks !
– Anto
Mar 08 '13 at 01:23
s
at the beginning stands for substitute
the /
are the delimiters which can, in theory, be any character as long that character doesn't need to be replaced and the $
mean "at the end of the string" which comes from regex
– Fuseteam
Dec 09 '20 at 12:07
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.-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.-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 of find
. 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" 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 regexBefore
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.
--execdir
did not pass in the leading ./
so the \.\/
part in the regex can be omitted. May that is because I am on OSX not ubuntu
– apollo
Sep 15 '19 at 05:41
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
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'
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.
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
file.3gp
to file.3gp.mp4
, which might not be what most people want.
– muru
Mar 13 '16 at 22:05
.3gp
or .mp4
here were just for illustration purposes.
– KhoPhi
Mar 15 '16 at 13:56
basename "$i" .mp4
to remove the previous extension instead of "ren-$i.mp4".
– TaoPR
May 13 '16 at 08:13
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
.edefg
-->.def
. – brasofilo May 18 '20 at 04:24