
Automating Media File Renaming: A Practical Bash Scripting Project
As part of my project documentation, I wanted to share a small but meaningful script I created during the file cleanup phase of one of my media archiving tasks.
The issue may seem minor at first glance: renaming a batch of media files, but the underlying process demonstrates how scripting can transform repetitive, error prone manual work into a reliable, automated workflow.
The Problem
While consolidating media assets for a documentation project, I encountered a folder full of photos and videos collected over several years from different devices. The files were inconsistently named: IMG_0012.JPG, Screenshot 2023 04 17 at 10.34.21.png, MOV_0001.mov, and so on.
I wanted a naming convention that:
- Used the file's true creation timestamp (not the modified or access time)
- Ensured each filename was unique
- Included standardized formatting:
DD_Mon_YYYY_at_HH.MM.SS - Worked across typical media types: images and videos
- Was idempotent: re running the script wouldn't cause unwanted renames
The Solution: A Bash Script with macOS Metadata Support
To address this, I wrote a Bash script that:
- Iterates over all files in a folder
- Filters only media files (
jpg,jpeg,png,gif,mov,mp4,avi) - Retrieves the file system creation date using the macOS
mdlscommand - Converts the timestamp to Asia Jakarta (WIB) time zone
- Constructs a unique filename like
07_Aug_2025_at_13.42.10_1.JPG, always appending a suffix - Skips non file items and unsupported file types
- Logs all actions to both terminal and a file (
renamed_log_always_suffixed.txt)
This script is designed for macOS only, due to its reliance on mdls and BSD style date.
Key Features
- Uses the original file creation date from macOS metadata (not the modified or access time)
- Adjusts timestamps to Asia Jakarta timezone (UTC plus 7)
- Accepts only common image and video formats
- Always appends a numeric suffix starting from _1
- Avoids renaming files that are already correctly named
- Writes detailed logs using tee for traceability
Sample Output
For a file originally named IMG_1234.JPG created on 7 August 2025 at 13:42:10, the script will rename it as:
07_Aug_2025_at_13.42.10_1.JPG
If a file with that name already exists, it tries:
07_Aug_2025_at_13.42.10_2.JPG
and so on, until it finds a unique name.
Why This Matters in the Project
This script became part of the final data preparation pipeline for a larger documentation and archival system. Proper naming is essential for:
- Indexing media files in static websites or content management systems
- Maintaining chronological order across files from different sources
- Preventing overwrites and accidental loss due to filename duplication
By automating the rename process, I was able to:
- Reduce manual error
- Save time during migration
- And ensure consistent formatting for downstream tooling
Source
You can view or adapt the script below. It is designed to be run in a Bash shell on macOS, and it assumes you have the necessary permissions to rename files in the target directory.:
bash <<'EOF' | tee renamed_log_always_suffixed.txt
# Set shell options for case-insensitive globbing and matching hidden files (if needed)
# For Bash:
shopt -s nocaseglob # Case-insensitive globbing
# For Zsh (often default for new users, but explicitly set if needed):
# setopt KSH_GLOB # or setopt NO_CASE_GLOB, NO_MARK_DIRS, etc.
# The `for f in *` then checking extension is generally safer and more portable for this.
# Process all files in the current directory
for f in *; do
# Check if it's a regular file
if [[ ! -f "$f" ]]; then
echo "--- Skipping non-file: $f ---"
continue
fi
# Extract extension in lowercase for easier checking
ext_lower=$(echo "${f##*.}" | tr '[:upper:]' '[:lower:]')
# List the extensions you want to process
case "$ext_lower" in
jpg|jpeg|png|gif|mov|mp4|avi)
# This is a file type we want to process
;;
*)
# Not a supported media file type, skip it
echo "--- Skipping unsupported file type: $f (extension .$ext_lower) ---"
continue
;;
esac
echo "--- Processing: $f ---"
# Get macOS file system creation date
fs_date_raw=$(mdls -name kMDItemFSCreationDate -raw "$f")
# Ensure fs_date_raw is not empty. If mdls fails, skip processing this file.
if [[ -z "$fs_date_raw" ]]; then
echo "Error: Could not retrieve file system date for $f. Skipping rename."
echo ""
continue
fi
# Set TZ to Western Indonesian Time (WIB) which is UTC+7 for formatting the date
export TZ='Asia/Jakarta'
# Format the file system date for the base part of the new name: "DD_Mon_YYYY_at_HH.MM.SS"
base_name_prefix=$(date -j -f "%Y-%m-%d %H:%M:%S %z" "$fs_date_raw" "+%d_%b_%Y_at_%H.%M.%S")
# Unset TZ
unset TZ
# Original file extension (maintains original case for the final rename)
original_ext="${f##*.}"
# Initialize the suffix number. It will *always* start at 1 for the first attempt.
current_suffix_number=1
final_target_name=""
# Loop to find a unique filename for the current file ($f)
while true; do
# Candidate name will always include a suffix starting from _1
candidate_name="${base_name_prefix}_${current_suffix_number}.${original_ext}"
# --- Check if the candidate name is available or if it's already our file ---
# 1. If the candidate name does NOT exist on disk, we found a unique name.
if [[ ! -e "$candidate_name" ]]; then
final_target_name="$candidate_name"
break
fi
# 2. If the candidate name DOES exist, check if it's actually the current file ($f).
# This handles cases where the script is run multiple times, and files are already renamed.
if [[ "$f" -ef "$candidate_name" ]]; then
final_target_name="$f"
break
fi
# 3. If the candidate name exists and is NOT the current file ($f),
# then it's a collision with another file. Increment the suffix number and try again.
((current_suffix_number++))
done
# --- Perform the rename action ---
if [[ "$f" != "$final_target_name" ]]; then
mv "$f" "$final_target_name"
echo "Renamed: $f -> $final_target_name"
else
echo "No change: $f (already has the target name or no unique name could be determined)."
fi
echo ""
done
EOF