Skip to content

Instantly share code, notes, and snippets.

@imCorfitz
Created July 1, 2025 13:46
Show Gist options
  • Save imCorfitz/40675c95e38fd7c5323793d14f40e542 to your computer and use it in GitHub Desktop.
Save imCorfitz/40675c95e38fd7c5323793d14f40e542 to your computer and use it in GitHub Desktop.
Convert files and directories to kebab-case
#!/bin/bash
# Script to convert all directory and file names to kebab-case
# Usage: ./convert-to-kebab-case.sh
set -e
# Function to convert string to kebab-case
to_kebab_case() {
local input="$1"
# Remove file extension temporarily
local extension=""
local basename="$input"
if [[ "$input" == *.* ]]; then
extension=".${input##*.}"
basename="${input%.*}"
fi
# Convert to kebab-case:
# 1. Replace camelCase with kebab-case (insert hyphen before uppercase letters)
# 2. Replace underscores and spaces with hyphens
# 3. Convert to lowercase
# 4. Remove multiple consecutive hyphens
local kebab=$(echo "$basename" | \
sed 's/\([a-z0-9]\)\([A-Z]\)/\1-\2/g' | \
sed 's/[_[:space:]]\+/-/g' | \
tr '[:upper:]' '[:lower:]' | \
sed 's/-\+/-/g' | \
sed 's/^-\|-$//g')
echo "${kebab}${extension}"
}
# Function to rename files and directories recursively
rename_items() {
local current_dir="$1"
# Process files first, then directories (to avoid path issues)
find "$current_dir" -depth -type f -not -path "*/node_modules/*" -not -path "*/.git/*" | while read -r filepath; do
local dir=$(dirname "$filepath")
local filename=$(basename "$filepath")
local new_filename=$(to_kebab_case "$filename")
if [[ "$filename" != "$new_filename" ]]; then
local new_filepath="$dir/$new_filename"
# Check if it's a case-only change on case-insensitive filesystem
if [[ "$(echo "$filename" | tr '[:upper:]' '[:lower:]')" == "$(echo "$new_filename" | tr '[:upper:]' '[:lower:]')" ]]; then
# Two-step rename for case-only changes
local temp_filepath="$dir/${filename}.tmp.$$"
echo "Renaming file (case-only): $filepath -> $new_filepath"
mv "$filepath" "$temp_filepath"
mv "$temp_filepath" "$new_filepath"
elif [[ ! -e "$new_filepath" ]]; then
echo "Renaming file: $filepath -> $new_filepath"
mv "$filepath" "$new_filepath"
else
echo "Warning: Target file already exists, skipping: $new_filepath"
fi
fi
done
# Process directories from deepest to shallowest
find "$current_dir" -depth -type d -not -path "*/node_modules/*" -not -path "*/.git/*" | while read -r dirpath; do
local parent_dir=$(dirname "$dirpath")
local dirname=$(basename "$dirpath")
local new_dirname=$(to_kebab_case "$dirname")
if [[ "$dirname" != "$new_dirname" ]]; then
local new_dirpath="$parent_dir/$new_dirname"
# Check if it's a case-only change on case-insensitive filesystem
if [[ "$(echo "$dirname" | tr '[:upper:]' '[:lower:]')" == "$(echo "$new_dirname" | tr '[:upper:]' '[:lower:]')" ]]; then
# Two-step rename for case-only changes
local temp_dirpath="$parent_dir/${dirname}.tmp.$$"
echo "Renaming directory (case-only): $dirpath -> $new_dirpath"
mv "$dirpath" "$temp_dirpath"
mv "$temp_dirpath" "$new_dirpath"
elif [[ ! -e "$new_dirpath" ]]; then
echo "Renaming directory: $dirpath -> $new_dirpath"
mv "$dirpath" "$new_dirpath"
else
echo "Warning: Target directory already exists, skipping: $new_dirpath"
fi
fi
done
}
# Confirm before proceeding
echo "This script will rename all files and directories to kebab-case."
echo "It will skip node_modules and .git directories."
echo "Make sure you have a backup of your project before proceeding!"
echo ""
read -p "Do you want to continue? (y/N): " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Operation cancelled."
exit 0
fi
# Get the directory to process (default to current directory)
target_dir="${1:-.}"
if [[ ! -d "$target_dir" ]]; then
echo "Error: Directory '$target_dir' does not exist."
exit 1
fi
echo "Processing directory: $target_dir"
echo ""
# Start the renaming process
rename_items "$target_dir"
echo ""
echo "Renaming complete!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment