Bash Batch Rename, with Rreview
As developers, we all have that time when we need to batch do something with our large tree of files. While there are batch utility out there, they may not be that easy to customize to our needs.
Since i use bash, and i know some Regular Expressions, i can use find
and sed
to generate commands for me.
I have been using this method for two years, and it saves me lots of clicking into/out of folders, with great features that grants me safety, knowing exactly what this script will do, before i actually run it.
(tl;dr: Skip to the Summary for the whole command)
Example: New translation for all of your submodules
Let’s say you have a lot of games that share the same folder structure, each has its pack of language assets, like below:
tree of ~/projectproject
|--game1
| |--lang
| | |--EN/ (4 files)
| | |--TR/ (4 files)
| | `--ZH/ (4 files)
| `--code and stuff
|--game2
| |--lang
| | |--EN/ (4 files)
| | |--TR/ (4 files)
| | `--ZH/ (4 files)
| `--code and stuff
|--game3
| |--lang
| | |--EN/ (4 files)
| | |--TR/ (4 files)
| | `--ZH/ (4 files)
| `--code and stuff
`--game4
|--lang
| |--EN/ (4 files)
| |--TR/ (4 files)
| `--ZH/ (4 files)
`--code and stuff
Your designer gave you a list of Japanese language assets, structured a bit differently:
tree of ~/newStufflang
|--game1/ (4 JP files)
|--game2/ (4 JP files)
|--game3/ (4 JP files)
`--game4/ (4 JP files)
Now you want to add these new JP language to each of the games, without having to go into each game (24 of them!) and manually copy a file.
If you are using bash
, i have a quite convenient way, pure bash script:
Find
all files you need
cd ~/newStuff
find -type f -path ./node_modules -prune -o -path ./dist -prune -o -wholename './lang/*'
This selects all paths in ~/newStuff
that fulfills the glob pattern
Breakdown:
find
: find in this directory-type f
: of type file-path ./node_modules -prune
: if encounter this directory, skip-o
else-path ./dist -prune
: if encounter that directory, skip-o
else-wholename './lang/*'
: with this name, match path (glob pattern)
(it is case sensitive. To use insensitive, use-iwholename
instead.)
(note: I did not use regex here, but you can use-regextype sed -regex ".*"
like this:
https://stackoverflow.com/questions/6844785/how-to-use-regex-with-find-command)
The result should look like this:
./lang/game1/text.json
./lang/game1/texture.png
./lang/game1/atlas.json
./lang/game1/icon.png
./lang/game2/text.json
./lang/game2/texture.png
./lang/game2/atlas.json
./lang/game2/icon.png
./lang/game3/text.json
./lang/game3/texture.png
./lang/game3/atlas.json
./lang/game3/icon.png
./lang/game4/text.json
./lang/game4/texture.png
./lang/game4/atlas.json
./lang/game4/icon.png
The nice thing about this command is that it gives you a list of paths in string. Now you can pipe|
it into sed
to make whatever command you choose:
Sed them into a list of commands
sed -r -e 's/.\/lang\/game(.+?)\/(.+)/cp -n & ~\/project\/game\1\/lang\/JP\/\2/'
sed breakdown:
-r
: replace-e
: use regex's/.../.../'
: the regex replace complex.\/lang\/game(.+?)\/(.+)
: reads./lang/game*/*
, capturing the first and second*
cp -n & ~\/project\/game\1\/lang\/JP\/\2/
: Replaces the above pattern into acp -n
command.cp -n
means no override&
means the whole match,\1
means the first capture group, and so on
using sed
, the first line…
./lang/game1/text.json
…will be transformed into:
cp -n ./lang/game1/text.json ~/project/game1/lang/JP/text.json
Putting all together
When you execute the whole find
sed
command:
find -type f -path ./node_modules -prune -o -path ./dist -prune -o -wholename './lang/*' | sed -r -e 's/.\/lang\/game(.+?)\/(.+)/cp -n & ~\/project\/game\1\/lang\/JP\/\2/'
the file list will be transformed into:
cp -n ./lang/game1/text.json ~/project/game1/lang/JP/text.json
cp -n ./lang/game1/texture.png ~/project/game1/lang/JP/texture.png
cp -n ./lang/game1/atlas.json ~/project/game1/lang/JP/atlas.json
cp -n ./lang/game1/icon.png ~/project/game1/lang/JP/icon.png
cp -n ./lang/game2/text.json ~/project/game2/lang/JP/text.json
cp -n ./lang/game2/texture.png ~/project/game2/lang/JP/texture.png
cp -n ./lang/game2/atlas.json ~/project/game2/lang/JP/atlas.json
cp -n ./lang/game2/icon.png ~/project/game2/lang/JP/icon.png
cp -n ./lang/game3/text.json ~/project/game3/lang/JP/text.json
cp -n ./lang/game3/texture.png ~/project/game3/lang/JP/texture.png
cp -n ./lang/game3/atlas.json ~/project/game3/lang/JP/atlas.json
cp -n ./lang/game3/icon.png ~/project/game3/lang/JP/icon.png
cp -n ./lang/game4/text.json ~/project/game4/lang/JP/text.json
cp -n ./lang/game4/texture.png ~/project/game4/lang/JP/texture.png
cp -n ./lang/game4/atlas.json ~/project/game4/lang/JP/atlas.json
cp -n ./lang/game4/icon.png ~/project/game4/lang/JP/icon.png
Now you can pipe |
the whole thing into bash
to run it:
Fire!
find -type f -path ./node_modules -prune -o -path ./dist -prune -o -wholename './lang/*' | sed -r -e 's/.\/lang\/game(.+?)\/(.+)/cp -n & ~\/project\/game\1\/lang\/JP\/\2/' | bash
And don’t cross your fingers because you knew what’s going to happen.
Summary
to preview:
find -type f -path ./node_modules -prune -o -path ./dist -prune -o -wholename './lang/*' | sed -r -e 's/.\/lang\/game(.+?)\/(.+)/cp -n & ~\/project\/game\1\/lang\/JP\/\2/'
to run:
find -type f -path ./node_modules -prune -o -path ./dist -prune -o -wholename './lang/*' | sed -r -e 's/.\/lang\/game(.+?)\/(.+)/cp -n & ~\/project\/game\1\/lang\/JP\/\2/' | bash
Other examples
As an example, let’s use the same ~/newStuff
and ~/project
Extract files into a folder, eg: for deploy
find -type f -path ./node_modules -prune -o -path ./dist -prune -o -wholename './lang/**/*.json' | sed -r -e 's/.\/lang\/game(.+?)\/.+/cp -n --parents & ~\/some_folder/'
This one copies all language .json files into some_folder
. Add | bash
to run
cp --parents
: create the intermediate parent directories if needed
Simple batch rename
For all .json files, Replaces the first occurrence of JP with EN. Add | bash
to run.
find -type f -path ./node_modules -prune -o -path ./dist -prune -o -wholename './lang/**/*.json' | sed -r -e 's/.\/(.+?)JP(.+?)\/.+/mv & .\/\1EN\2/'
This is my way of using these commands. To be honest, they kind of feels too long.
Let me know if you have better styles in doing batch work in bash.