Rename files and directories recursively under ubuntu /bash
Try doing this (require bash --version
>= 4):
shopt -s globstar
rename -n 's/special/regular/' **
Remove the -n
switch when your tests are OK
There are other tools with the same name which may or may not be able to do this, so be careful.
If you run the following command (GNU
)
$ file "$(readlink -f "$(type -p rename)")"
and you have a result like
.../rename: Perl script, ASCII text executable
and not containing:
ELF
then this seems to be the right tool =)
If not, to make it the default (usually already the case) on Debian
and derivative like Ubuntu
:
$ sudo update-alternatives --set rename /path/to/rename
(replace /path/to/rename
to the path of your perl's rename
command.
If you don't have this command, search your package manager to install it or do it manually
Last but not least, this tool was originally written by Larry Wall, the Perl's dad.
how to rename folders recursively in linux
As far as I understand correctly, you don't want to rename files "recursively", but you want to rename multiple files.
Maybe it is not the best possibility to do this, but I would do it like this:
for a in Test*; do mv $a `echo $a | sed -e s/Test/Products/`; done
Explanation:
for a in Test*; do <some command> ; done
This will perform some command for each file matching the pattern Test*
. $a
is the placeholder for the file name.
echo $a | sed -e s/Test/Products/
This command prints the file name while Test
is replaced by Products
. If the file name ($a
) is Test123
, the command will print Products123
.
mv $a `<some command>`
This command renames the file $a
. The new file name is the text that is printed by the command <some command>
.
If you really want to perform the renaming recursively (in all sub-directories), you might use the find
command:
for a in `find . | grep Test`; do ...
EDIT
I want to rename the folders and not the files
A folder is some kind of "file" in Linux; what you call "file" is called "regular file" in Linux. And the commands above do not distinguish between folders and "regular files".
So if your parent folder does not contain any "regular file" (or symbolic link or pipe ...) matching the name pattern Test*
, you can use the commands above.
If your parent folder contains both sub-folders and other types of files (for example "regular files" or symbolic links) that match the name pattern Test*
and you only want to rename the sub-folders, you may use the find
command to select all folders in the current parent folder:
for a in `find -maxdepth 1 -type d | grep /Test`; do ...
Edit 2
I didn't know the rename
command mentioned in the answer linked in one of the comments to your question. And the rename
tool seems not to be available in every Linux distribution.
Using rename
you might do it the following way:
rename Test Product `find -maxdepth 1 -type d`
This will search for all sub-directories in the current directory and replace Test
by Product
in the name. The sub-directory named MyTest5
becomes MyProduct5
.
However, a regular file named Test6
will not be renamed.
If you only want to rename directories whose name starts with Test
, you may do it the following way:
rename Test Product `find -maxdepth 1 -type d | grep /Test`
Test5
will become Product5
but MyTest5
will not be renamed.
Unfortunately, I haven't used the rename
tool myself, so I'm not sure if this really works.
Recursive bash script to rename files and folders with specific rules on Mac/Linux
Here is a working solution I have come up with so far which needs to be optimised and cleaned up, its rudimentary and I can use optimisation tips:
Solution 1 - GitHub - WIP
#!/bin/bash
set -e
################################
## WORKING
##
## TODO: optimise
##
## WIP: [optimisation 1:]
## Instead of each function looping and going through all files and folders
## have one function to do the loop and pass the files and folders as arguments
## to all the other functions.
## Continue developing function `rename_files_and_folders_dirs_1`.
##
## [optimisation 2:]
## Expand features to take user input.
## For example call the script with flags/arguments/parameters/options
## which invoke different functions passing different arguments
## such as tags to remove or tags to add.
## Note: This could likely make [optimisation 1:] redundant, so need to choose
## witch path to follow.
##
## References
## https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion
## https://stackoverflow.com/questions/15012631/rename-files-and-directories-recursively-under-ubuntu-bash
## https://superuser.com/questions/213134/recursively-rename-files-change-extension-in-linux
## https://stackoverflow.com/questions/6509650/extract-directory-from-path
## https://stackoverflow.com/questions/6121091/get-file-directory-path-from-file-path/6121114
## https://stackoverflow.com/questions/13210880/replace-one-substring-for-another-string-in-shell-script
## https://tldp.org/LDP/abs/html/string-manipulation.html
## https://stackoverflow.com/questions/16623835/remove-a-fixed-prefix-suffix-from-a-string-in-bash
## https://unix.stackexchange.com/questions/311758/remove-specific-word-in-variable
## https://unix.stackexchange.com/questions/56810/adding-text-to-filename-before-extension
## https://stackoverflow.com/questions/45799657/bash-adding-a-string-to-file-name
## https://linuxize.com/post/bash-functions/
## https://bash.cyberciti.biz/guide/Pass_arguments_into_a_function
## https://stackoverflow.com/questions/6212219/passing-parameters-to-a-bash-function
## https://linuxacademy.com/blog/linux/conditions-in-bash-scripting-if-statements/
################################
declare -a STRINGS_TO_REPLACE
STRINGS_TO_REPLACE=("tag1" "tag2")
STRING_TO_ADD_IF_NOT_PRESENT="tag3"
################################
rename_files_remove_old_tags_arguments() {
filepathnodot="${1#.}"
# echo "$filepathnodot"
justfilenamenopath="${1##*/}"
# echo "$justfilenamenopath"
justpathnofile=${1%/*}
# echo "$justpathnofile"
for current_string in "${STRINGS_TO_REPLACE[@]}" ;
do
if [[ "$justfilenamenopath" == *"$current_string"* ]];
then
# echo "Will rename $justfilenamenopath"
test -e "$1" &&
newfilename=$(echo "$justfilenamenopath" | sed "s/$current_string//g")
mv -v "$1" "$justpathnofile/$newfilename"
break;
fi
done
}
rename_files_remove_old_tags_arguments
################################
rename_files_remove_old_tags() {
while IFS= read -r -d '' n; do
filepathnodot="${n#.}"
# echo "$filepathnodot"
justfilenamenopath="${n##*/}"
# echo "$justfilenamenopath"
justpathnofile=${n%/*}
# echo "$justpathnofile"
for current_string in "${STRINGS_TO_REPLACE[@]}" ;
do
if [[ "$justfilenamenopath" == *"$current_string"* ]];
then
# echo "Will rename $justfilenamenopath"
test -e "$n" &&
newfilename=$(echo "$justfilenamenopath" | sed "s/$current_string//g")
mv -v "$n" "$justpathnofile/$newfilename"
break;
fi
done
done < <(find . \( -type f -name "[!.]*" \) -print0)
}
rename_files_remove_old_tags
################################
rename_folders_dirs_remove_old_tags() {
while IFS= read -r -d '' n; do
for current_string in "${STRINGS_TO_REPLACE[@]}" ;
do
if [[ "$n" == *"$current_string"* ]];
then
# echo "Will rename $n"
test -e "$n" &&
newfilename=$(echo "$n" | sed "s/$current_string//g")
mv -v "$n" "$newfilename"
break;
fi
done
done < <(find . \( -type d -name "[!.]*" \) -print0)
}
rename_folders_dirs_remove_old_tags
################################
rename_files_add_new_tags() {
while IFS= read -r -d '' n; do
filepathnodot="${n#.}"
# echo "$filepathnodot"
justfilenamenopath="${n##*/}"
# echo "$justfilenamenopath"
justpathnofile=${n%/*}
# echo "$justpathnofile"
if [[ ! "$justfilenamenopath" == *"$STRING_TO_ADD_IF_NOT_PRESENT"* ]];
then
# echo "Will rename $justfilenamenopath"
test -e "$n" &&
newfilename="${justfilenamenopath%.*} $STRING_TO_ADD_IF_NOT_PRESENT.${justfilenamenopath##*.}"
mv -v "$n" "$justpathnofile/$newfilename"
fi
done < <(find . \( -type f -name "[!.]*" \) -print0)
}
rename_files_add_new_tags
################################
rename_folders_dirs_add_new_tags() {
while IFS= read -r -d '' n; do
filepathnodot="${n#.}"
# echo "$filepathnodot"
justpathnofile=${n%/*}
# echo "$justpathnofile"
if [[ ! "$n" == *"$STRING_TO_ADD_IF_NOT_PRESENT"* ]];
then
test -e "$n" &&
newfilename="$n $STRING_TO_ADD_IF_NOT_PRESENT"
mv -v "$n" "$newfilename"
fi
done < <(find . \( -type d -name "[!.]*" \) -print0)
}
rename_folders_dirs_add_new_tags
################################
################################
################################
rename_files_and_folders_dirs_1 () {
while IFS= read -r -d '' n; do
if [[ -f $n ]];
then
# echo "FILE <<< $n"
rename_files_remove_old_tags_arguments "$n"
# rename_files_add_new_tags "$n"
elif [[ -d "$n" ]];
then
echo "DIR >>> $n"
# rename_folders_dirs_remove_old_tags "$n"
# rename_folders_dirs_add_new_tags "$n"
fi
done < <(find . \( -name "[!.]*" \) -print0)
}
# rename_files_and_folders_dirs_1
################################
################################
################################
recursively rename directories in bash
Try the following code using parameter expansion
find . -type d -iname '*foo*' -depth -exec bash -c '
echo mv "$1" "${1//[Ff][Oo][Oo]/BAr}"
' -- {} \;
But your best bet will be the prename
command (sometimes named rename
or file-rename
)
find . -type d -iname '*foo*' -depth -exec rename 's@Foo@Bar@gi' {} +
And if you are using bash4
or zsh
(**
mean recursive):
shopt -s globstar
rename -n 's@Foo@Bar@gi' **/*foo*/
If it fit your needs, remove the -n
(dry run) switch to rename for real.
SOME DOC
rename was originally written by Perl's dad, Larry Wall himself.
Find multiple files and rename them in Linux
You can use find
to find all matching files recursively:
$ find . -iname "*dbg*" -exec rename _dbg.txt .txt '{}' \;
EDIT: what the '{}'
and \;
are?
The -exec
argument makes find execute rename
for every matching file found. '{}'
will be replaced with the path name of the file. The last token, \;
is there only to mark the end of the exec expression.
All that is described nicely in the man page for find:
-exec utility [argument ...] ;
True if the program named utility returns a zero value as its
exit status. Optional arguments may be passed to the utility.
The expression must be terminated by a semicolon (``;''). If you
invoke find from a shell you may need to quote the semicolon if
the shell would otherwise treat it as a control operator. If the
string ``{}'' appears anywhere in the utility name or the argu-
ments it is replaced by the pathname of the current file.
Utility will be executed from the directory from which find was
executed. Utility and arguments are not subject to the further
expansion of shell patterns and constructs.
Rename a portion of a filename recursively throughout directory and sub directory
Probably you want to rename only the filename (last pathname component), not a inbetween subdirectory name. Then this task can be accomplished using globstar
feature of bash.
#!/bin/bash
shopt -s globstar
for pathname in ./**/*foo*; do
[[ -f $pathname ]] || continue
basename=${pathname##*/}
mv "$pathname" "${pathname%/*}/${basename//foo/bar}"
done
Find and replace filename recursively in a directory
You can do it this way:
find . -name '123_*.txt' -type f -exec sh -c '
for f; do
mv "$f" "${f%/*}/${f##*/123_}"
done' sh {} +
No pipes, no reads, no chance of breaking on malformed filenames, no non-standard tools or features.
rename folder/directory recursively
find . -depth -name '*a_*' -execdir bash -c 'mv "$0" "${0//a_/b_}"' {} \;
The -depth
switch is important so that the directory content is processed before the directory itself! otherwise you'll run into problems :)
.
100% safe regarding filenames with spaces or other funny symbols.
Recursively rename files using find and sed
This happens because sed
receives the string {}
as input, as can be verified with:
find . -exec echo `echo "{}" | sed 's/./foo/g'` \;
which prints foofoo
for each file in the directory, recursively. The reason for this behavior is that the pipeline is executed once, by the shell, when it expands the entire command.
There is no way of quoting the sed
pipeline in such a way that find
will execute it for every file, since find
doesn't execute commands via the shell and has no notion of pipelines or backquotes. The GNU findutils manual explains how to perform a similar task by putting the pipeline in a separate shell script:
#!/bin/sh
echo "$1" | sed 's/_test.rb$/_spec.rb/'
(There may be some perverse way of using sh -c
and a ton of quotes to do all this in one command, but I'm not going to try.)
Related Topics
Can Awk Patterns Match Multiple Lines
Why Is a Tilde in a Path Not Expanded in a Shell Script
Shell Command to Tar Directory Excluding Certain Files/Folders
How to Show All Shared Libraries Used by Executables in Linux
How to Setup & Run Phantomjs on Ubuntu
How to Insert a Text At the Beginning of a File
Run an Untrusted C Program in a Sandbox in Linux That Prevents It from Opening Files, Forking, etc.
How to Set Linux Environment Variables With Ansible
What Is the Maximum Size of a Linux Environment Variable Value
"No Such File or Directory" Error When Executing a Binary
How to Configure Qt For Cross-Compilation from Linux to Windows Target