Here's the current structure of my directory.
.
├── Show 1
│ ├── Season 1
│ └── Season 2
├── Show 2
├── Season 1
└── Season 2
I want to rename the Season folders to include the name of the Show. The desired structure looks like this :
.
├── Show 1
│ ├── Show 1 - Season 1
│ └── Show 1 - Season 2
├── Show 2
├── Show 2 - Season 1
└── Show 2 - Season 2
Here's the script I wrote :
# Parse through all show folders.
for show in /Users/sanjeetsuhag/Desktop/* ; do
# Check if it is a folder.
if [ -d "$show" ]; then
# Parse through all season folders.
for season in $show/* ; do
# Check if it is a folder.
if [ -d "$season" ]; then
mv $season "$show/$(basename "$show") - $(basename "$season")"
fi
done
fi
done
This is my first time scripting in Bash. Is there any thing I could improve ?
2 Answers 2
You should always double-quote variables that are used as filesystem paths. This is the same script but with variables correctly double-quoted:
# Parse through all show folders.
for show in /Users/sanjeetsuhag/Desktop/*; do
# Check if it is a folder.
if [ -d "$show" ]; then
# Parse through all season folders.
for season in "$show"/*; do
# Check if it is a folder.
if [ -d "$season" ]; then
mv "$season" "$show/$(basename "$show") - $(basename "$season")"
fi
done
fi
done
If you change the globs to end with /,
they will match only directories,
and so you can skip the directory checks:
for show in /Users/sanjeetsuhag/Desktop/*/; do
for season in "$show"/*/; do
mv "$season" "$show/$(basename "$show") - $(basename "$season")"
done
done
You could further optimize this by using pattern substitution instead of basename:
for show in /Users/sanjeetsuhag/Desktop/*/; do
for season in "$show"/*/; do
show=${show%/}
season=${season%/}
mv "$season" "$show/${show##*/} - ${season##*/}"
done
done
The ${show%/} is to shave off the trailing /,
and the ${show##*/} is to delete everything until the last /.
Finally, instead of hardcoding the base path /Users/sanjeetsuhag/Desktop,
the script would be more reusable if you make that a command line parameter of the script.
To avoid problems with spaces, as warned by @chicks, and most other hazardous characters as well, use double quotes often. The find utility also has a null-terminated line option -print0 that helps with space and newline problems.
Here's a revamped script to try. Liberally commented as this is your first time into Bash scripting. Hope it helps you learn, and works as intended for your project.
#!/bin/bash
# Convert this directory structure:
# .
# ├── Show 1
# │ ├── Season 1
# │ └── Season 2
# └── Show 2
# ├── Season 1
# └── Season 2
# to this structure:
# .
# ├── Show 1
# │ ├── Show 1 - Season 1
# │ └── Show 1 - Season 2
# └── Show 2
# ├── Show 2 - Season 1
# └── Show 2 - Season 2
# or, optionally to this structure:
# .
# ├── Show 1
# │ ├── Show 1 - Season 1
# │ │ ├── Show 1 - Season 1 - Episode 1
# │ │ ├── Show 1 - Season 1 - Episode 2
# │ │ └── Show 1 - Season 1 - Episode 3
# │ └── Show 1 - Season 2
# │ ├── Show 1 - Season 1 - Episode 1
# │ ├── Show 1 - Season 2 - Episode 2
# │ └── Show 1 - Season 2 - Episode 3
# └── Show 2
# ├── Show 2 - Season 1
# │ ├── Show 2 - Season 1 - Episode 1
# │ ├── Show 2 - Season 1 - Episode 2
# │ └── Show 2 - Season 1 - Episode 3
# └── Show 2 - Season 2
# ├── Show 2 - Season 2 - Episode 1
# ├── Show 2 - Season 2 - Episode 2
# └── Show 2 - Season 2 - Episode 3
# Is safe for spaces, and most other shell characters. Notable exception
# is the "\" charcter which WILL NOT work in the directoy names anywhere.
# !@#$&*"'{}[](): are all safe, seemingly international chars are as well.
#
# TEST on a backup copy first!! YMMV
#
# Get the show directory names using find
# -maxdepth 1 : limit search to 1 level - the current directory
# return values are ./show
# -type d : only find directories
# -print0 : print the list as null-terminate strings
# pipe stdout to read
# -d $'0円' : make read use null as the line delimiter
find . -maxdepth 1 -type d -print0 | while read -d $'0円' show;
do
# Skip the . directory entry for the shows
if [ ! "." = "$show" ];
then
# Get the season directory names using find as above
# This time use the current show directory as the base directory
# Return values are ./show/season
find "$show" -maxdepth 1 -type d -print0 | while read -d $'0円' show_season;
do
# Strip of path from the show, in this case just ./
show=$(basename "$show");
# Skip the . directory entry for the seasons
if [ ! "./$show" = "$show_season" ]
then
# Strip of the path from the season, in this case ./show/
season_name=$(basename "$show_season");
# Section to process the episodes in each season directory under the same name expansion scheme
# Simply remove the comment marks in the first column, leave those that are deeper in.
# Get the episode file names using the above logic again
# This time use the current season directory as the base directory
# Return values are ./show/season/episode
# find "$show_season" -maxdepth 1 -type f -print0 | while read -d $'0円' show_season_episode;
# do
# if [ ! "$show_season" = "$show_season_episode" ]
# then
# # Strip of the path from the episode, in this case ./show/season/
# episode_name=$(basename "$show_season_episode");
# # Rename the episode, the season directory has NOT been renamed yet!
# mv "$show_season_episode" "$show_season/$show - $season_name - $episode_name";
# fi
# done
mv "$show_season" "$show/$show - $season_name";
fi
done
fi
done
*expansion on yourforlines will cause problems if you have a space in a file name or directory since the shell splits things on whitespace. \$\endgroup\$