Score:4

How to mass change many files names using find and mv

mn flag

I have a lot of files in many directories that are wrongly spelled.

They are spelled Localisation.json and I want to change them to Localization.json.

I tried this, but it does not work:

find -type f -name Localis* | xargs mv Localization.json
Score:13
jp flag

Notice

First, find -type f -name Localis* | xargs mv Localization.json will never work ... You need something like xargs -I {} mv {} Localization.json or set the xargs's option -n 1.

Second, mv is not the right choice here as it will result in all files named e.g. Localisation.json(or whatever files find matches and outputs) in all sub-directories of the current working directory moved to the current working directory and renamed as Localization.json overwriting this file every time ... e.g. find might output something like ./subdirectory/Localisation.json and that will translate with mv like so:

mv ./subdirectory/Localisation.json Localization.json

changing destination directory to the current working directory ... You, definitely, don't want that.

Therefore, ...

Suggestion

You can format find's output to be NULL delimited(To try to avoid breaking filenames containing e.g. whitespace) with -print0 then pipe it to xargs -0 with rename like so:

find -type f -name "Localisation.json" -print0 | xargs -0 rename -n 's/Localisation\.json$/Localization.json/'

Or. even better, and comparatively efficient ... you can use find's own -exec with rename like so:

find -type f -name "Localisation.json" -exec rename -n 's/Localisation\.json$/Localization.json/' {} \+

and it's important to quote the argument to -name e.g. -name "Localis*"

Notice that everything in the CurrentName part of rename's 's/CurrentName/NewName/' is considered a regular expression ... Hence the escaped dot \. to match a literal . and the $ to match the end of the line excluding directories from being renamed in case you have a parent directory with the same filename e.g. Localisation.json/Localisation.json(That can also be achieved with the rename's option --nopath)

The rename's option -n will only simulate renaming ... Remove it when satisfied with the output to do the actual renaming.

Workaround

You can use find's -execdir(Which runs the specified command from the subdirectory containing the matched file) with mv like so:

find -type f -name "Localisation.json" -execdir echo mv -n -- {} Localization.json \;

You can, also, use find's -exec with mv in a bash command string utilizing bash's parameter expansion and achieve the same result like so:

find -type f -name "Localisation.json" -exec bash -c 'echo mv -n -- "$1" "${1/%Localisation.json/Localization.json}"' _ {} \;

That will do only a renaming dry-run(simulation) ... Remove echo when satisfied with output to do the actual renaming.

Alternatively in bash, you can use recursive shell globbing and parameter expansion with only mv to achieve the same result like so:

shopt -s globstar; for f in **/Localisation.json; do
    # Renaming dry-run(simulation) ... Remove "echo" when satisfied with output to do the actual renaming.
    echo mv -n -- "$f" "${f/%Localisation.json/Localization.json}"
done; shopt -u globstar
vanadium avatar
cn flag
Not sure whether quoting the argument of `-name` with double quotes is a good idea. You do not want the shell to expand the wildcards. You want to pass the argument unchanged to `find`. So use single quotes.
Raffa avatar
jp flag
@vanadium Thank you for your feedback ... But no ... It won't be expanded by the shell when double quoted(*or single quoted ... Same behavior*) ... You can verify that if you enable bash command tracing with `set -x` then run it to see that it is passed to `find` without shell expansion :-)
vanadium avatar
cn flag
Strange! Why does't the shell expand? It likely will if multiple files present in the current directory match, and then you will have a problem ;)
Raffa avatar
jp flag
@vanadium Also no :-) ... It will be passed to `find` in single quotes both-ways ... So no shell expansion is going to happen that way in our lifetimes AFAIK :-) ... The shell expansion will, however happen **only** if not quoted at all and globs present in the filename.
vanadium avatar
cn flag
I see, wildcards are only expanded when outside of any quotes - it is variables that are being expanded when within double quotes.
muru avatar
us flag
To get around the problem of `mv` moving everything to the current working directory, use `-execdir`
Raffa avatar
jp flag
@muru Added that ... Thank you for your feedback.
user535733 avatar
cn flag
+1 for providing safe solution: "*The rename's option -n will only simulate renaming ... Remove it when satisfied with the output to do the actual renaming.*" The mark of a true professional.
Score:4
cn flag

In most cases, the easiest and most efficient way to rename files is by the command rename. To do it recursively within Bash shell you need to enable the globstar option:

shopt -s globstar

Then you can use the rename command in this way:

rename -n --nopath 's/Localisation\.json/Localization.json/' **/*
  • Remove the -n option to disable the dry-run mode and do the actual rename.

You might want to disable the globstar option afterwards for good measures with:

shopt -u globstar

The above rename command should work out of the box (without enabling additional options) in Zsh shell.

However, although less efficient this way as mv must be invoked for every single argument, you can pipe find's output to xargs with mv in a sh command string like so:

find -type f -name 'Localisation.json' -print0 | xargs -0 -I {} -- sh -c 'echo mv -n -- "$1" $(dirname "$1")/Localization.json' _ {}

echo is for dry-run only. Remove echo when satisfied with the output to do the actual renaming.

Raffa avatar
jp flag
I did not refresh the page so didn't notice your answer :-D ... Removed that part anyway ... +1 and you might find the rename's option `--nopath` useful.
pa4080 avatar
cn flag
Thanks, @Raffa. I will add a note about `--nopath` later. Meanwhile I also have upvoted your answer :) In addition I've realized the task could be solved by `find` and `xargs`, but the command is far complicated and I don't have time to explain it today: `find . -type f -name 'Localisation.json' -print0 | xargs -n1 -0 -I@ -- sh -c 'echo mv @ $(dirname @)/Localization.json'` (remove `echo`)... so if you have time feel free to include it in your answer or throw a new one :)
Raffa avatar
jp flag
That's nice but you're overloading it with options ... I think `find -type f -name 'Localisation.json*' -print0 | xargs -0 -I@ -- sh -c 'echo mv @ $(dirname @)/Localization.json'` will do fine :-) and it's smart ... I like it
marcelm avatar
cn flag
It would be better to run the command just on the files that need renaming. Now if there's a file called `BritishLocalisation.json`, it will get renamed, too. And if you limit the command to files called exactly `Localisation.json`, a simpler expression would suffice so you don't have to repeat the filename: `rename -n s/s/z/ **/Localisation.json` :)
Raffa avatar
jp flag
I had some free time and edited your answer with your comments ... I hope I didn't miss something :-)
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.