Rename Files and Directories Recursively Under Ubuntu /Bash

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

warning 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



Leave a reply



Submit