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.
Ask Ubuntu is a question and answer site for Ubuntu users and developers. It only takes a minute to sign up.
Sign up to join this communityA 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/' {} +
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
.
Apr 9, 2019 at 12:26
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.
Apr 19, 2011 at 18:35
s
at the beginning, and what other options can I use in there. Thanks !
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
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
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'
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.
.3gp
or .mp4
here were just for illustration purposes.
basename "$i" .mp4
to remove the previous extension instead of "ren-$i.mp4".
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
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.
.edefg
-->.def
.