Using a while
+ read
loop, Process Substitution and find
plus mv
to rename the files.
The script.
#!/usr/bin/env bash
shopt -s extglob nullglob
while IFS= read -rd '' directory; do
if [[ -e $directory && -x $directory ]] ; then
(
printf 'Entering directory %s\n' "$directory"
cd "$directory" || exit
files=(*.zip)
(( ${#files[*]} )) || {
printf 'There are no files ending in *.zip here!, moving on...\n'
continue
}
for file_name_with_extension in *.zip; do
extension=${file_name_with_extension##*.}
file_name_without_extension=${file_name_with_extension%."$extension"}
change_spaces_to_underscore="${file_name_without_extension//+([[:space:]])/_}"
remove_everything_that_is_not_alnum_and_under_score="${change_spaces_to_underscore//[![:alnum:]_]}"
change_every_underscore_with_a_single_under_score="${remove_everything_that_is_not_alnum_and_under_score//+(_)/_}"
new_file_name="$change_every_underscore_with_a_single_under_score.$extension"
mv -v "$file_name_with_extension" "${new_file_name::80}"
done
)
fi
done < <(find . ! -name . -type d -print0)
The script for creating dummy directories and files.
#!/usr/bin/env bash
mkdir -p foo/bar/baz/more/qux/sux
cd foo/ && touch 'Mean 18 - Golf Menu [SW] (1988)(Robert J. Butler) [Sports, Golf, Utility].zip'
cd bar/ && touch 'Mean 18 - M18 (1988)(Ken Hopkins) [Sports, Golf, Utility].zip'
cd baz/ && touch 'Metaltech- Battledrome Game Editor (1994)(Sierra On-Line, Inc.) [Utility].mp4'
cd more/ && touch 'Might and Magic III Character Editor (1991)(Blackbeard'\''s Ghost) [Utility].zip'
cd qux/ && touch 'Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].zip'
cd sux/ && touch 'Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].jpg'
Checking the directory tree with tree
tree foo/
foo/
├── bar
│ ├── baz
│ │ ├── Metaltech- Battledrome Game Editor (1994)(Sierra On-Line, Inc.) [Utility].mp4
│ │ └── more
│ │ ├── Might and Magic III Character Editor (1991)(Blackbeard's Ghost) [Utility].zip
│ │ └── qux
│ │ ├── Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].zip
│ │ └── sux
│ │ └── Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].jpg
│ └── Mean 18 - M18 (1988)(Ken Hopkins) [Sports, Golf, Utility].zip
└── Mean 18 - Golf Menu [SW] (1988)(Robert J. Butler) [Sports, Golf, Utility].zip
5 directories, 6 files
Using find
to print the files.
find foo/ ! -name . -type f
The output is
foo/Mean 18 - Golf Menu [SW] (1988)(Robert J. Butler) [Sports, Golf, Utility].zip
foo/bar/Mean 18 - M18 (1988)(Ken Hopkins) [Sports, Golf, Utility].zip
foo/bar/baz/more/Might and Magic III Character Editor (1991)(Blackbeard's Ghost) [Utility].zip
foo/bar/baz/more/qux/sux/Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].jpg
foo/bar/baz/more/qux/Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].zip
foo/bar/baz/Metaltech- Battledrome Game Editor (1994)(Sierra On-Line, Inc.) [Utility].mp4
Running the script inside the top level directory print something like:
Entering directory ./foo
mv -v Mean 18 - Golf Menu [SW] (1988)(Robert J. Butler) [Sports, Golf, Utility].zip Mean_18_Golf_Menu_SW_1988Robert_J_Butler_Sports_Golf_Utility.zip
Entering directory ./foo/bar
mv -v Mean 18 - M18 (1988)(Ken Hopkins) [Sports, Golf, Utility].zip Mean_18_M18_1988Ken_Hopkins_Sports_Golf_Utility.zip
Entering directory ./foo/bar/baz
There are no files ending in *.zip here!, moving on...
Entering directory ./foo/bar/baz/more
mv -v Might and Magic III Character Editor (1991)(Blackbeard's Ghost) [Utility].zip Might_and_Magic_III_Character_Editor_1991Blackbeards_Ghost_Utility.zip
Entering directory ./foo/bar/baz/more/qux
mv -v Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].zip Might_Magic_3_Character_viewereditor_v11_1991Mark_Betz_and_Chris_Lampton_Editor.
Entering directory ./foo/bar/baz/more/qux/sux
There are no files ending in *.zip here!, moving on...
- Remove the
echo
if you're satisfied with the output in order for mv
to rename the files.
Without the echo
the output is something like:
Entering directory ./foo
renamed 'Mean 18 - Golf Menu [SW] (1988)(Robert J. Butler) [Sports, Golf, Utility].zip' -> 'Mean_18_Golf_Menu_SW_1988Robert_J_Butler_Sports_Golf_Utility.zip'
Entering directory ./foo/bar
renamed 'Mean 18 - M18 (1988)(Ken Hopkins) [Sports, Golf, Utility].zip' -> 'Mean_18_M18_1988Ken_Hopkins_Sports_Golf_Utility.zip'
Entering directory ./foo/bar/baz
There are no files ending in *.zip here!, moving on...
Entering directory ./foo/bar/baz/more
renamed 'Might and Magic III Character Editor (1991)(Blackbeard'\''s Ghost) [Utility].zip' -> 'Might_and_Magic_III_Character_Editor_1991Blackbeards_Ghost_Utility.zip'
Entering directory ./foo/bar/baz/more/qux
renamed 'Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].zip' -> 'Might_Magic_3_Character_viewereditor_v11_1991Mark_Betz_and_Chris_Lampton_Editor.'
Entering directory ./foo/bar/baz/more/qux/sux
There are no files ending in *.zip here!, moving on...
This would be much better if we could convert sequences of unwanted character to one single underscore. Such as, instead of: XArchRogueTool(1984)(Unknown)[Utility].zip Could the output be:
X_Arch_Rogue_Tool_(1984)_(Unknown)_[Utility].zip?
Change the value of remove_everything_that_is_not_alnum_and_under_score
from:
remove_everything_that_is_not_alnum_and_under_score="${change_spaces_to_underscore//[![:alnum:]_]}"
to
remove_everything_that_is_not_alnum_and_under_score="${change_spaces_to_underscore//[![:alnum:]_()\[\]]}"
To exclude parenthesis ( )
and brackets [ ]
Add the code below the line where change_every_underscore_with_a_single_under_score
is at.
insert_underscore_in_between_parens="${change_every_underscore_with_a_single_under_score//')('/')_('}"
Change the value of new_file_name=
to "$insert_underscore_in_between_parens.$extension"
new_file_name="$insert_underscore_in_between_parens.$extension"
Pointing the directory to the script requires a bit of modification.
Add the code below after the shebang
directory_to_process="$1"
if [[ ! -e "$directory_to_process" ]]; then
printf >&2 '%s no such file or directory!\n' "$directory_to_process"
exit 1
elif [[ ! -d "$directory_to_process" ]]; then
printf >&2 '%s does not appear to be a directory!\n' "$directory_to_process"
exit 1
fi
Then change the .
from find
find "$directory_to_process" ! -name . -type d -print0
The new script.
#!/usr/bin/env bash
directory_to_process="$1"
if [[ ! -e "$directory_to_process" ]]; then
printf >&2 '[%s] no such file or directory!\n' "$directory_to_process"
exit 1
elif [[ ! -d "$directory_to_process" ]]; then
printf >&2 '[%s] does not appear to be a directory!\n' "$directory_to_process"
exit 1
fi
shopt -s extglob nullglob
while IFS= read -rd '' directory; do
if [[ -e $directory && -x $directory ]] ; then
(
printf 'Entering directory %s\n' "$directory"
cd "$directory" || exit
files=(*.zip)
(( ${#files[*]} )) || {
printf 'There are no files ending in *.zip here!, moving on...\n'
continue
}
for file_name_with_extension in *.zip; do
extension=${file_name_with_extension##*.}
file_name_without_extension=${file_name_with_extension%."$extension"}
change_spaces_to_underscore="${file_name_without_extension//+([[:space:]])/_}"
remove_everything_that_is_not_alnum_and_under_score="${change_spaces_to_underscore//[![:alnum:]_()\[\]]}"
change_every_underscore_with_a_single_under_score="${remove_everything_that_is_not_alnum_and_under_score//+(_)/_}"
insert_underscore_in_between_parens="${change_every_underscore_with_a_single_under_score//')('/')_('}"
new_file_name="$insert_underscore_in_between_parens.$extension"
echo mv -v "$file_name_with_extension" "${new_file_name:0:80}"
done
)
fi
done < <(find "$directory_to_process" ! -name . -type d -print0)
Now you give the directory as an argument to the script. e.g.
./script.sh foo/
Or an absolute path.
./script.sh /path/to/foo
If you add the script to your PATH and make it executable then you can.
script.sh /path/to/foo
Assuming your script name is script.sh
and the directory you want to process is named foo