Automate File Case Conversion: Scripts, CLI Tools & Best Practices
Overview
Automating filename case conversion saves time and avoids errors when normalizing large numbers of files (e.g., making all filenames lowercase, converting to snakecase, or title case). Common goals: consistency, cross-platform compatibility, preserving file extensions, and avoiding name collisions.
Tools & Methods
- Shell (bash/zsh) scripts — lightweight for macOS/Linux and WSL on Windows.
- PowerShell — native on Windows, cross-platform via PowerShell Core.
- Python scripts — portable, great for complex rules and Unicode handling.
- Command-line utilities:
- rename (Perl) — flexible on many Linux distros.
- mmv — move/rename in bulk using patterns.
- git mv — preserve history in repositories.
- util-linux’s rename (sometimes different semantics).
- Batch tools & GUI apps — e.g., Bulk Rename Utility (Windows), pyRenamer (Linux), Finder/Automator workflows (macOS).
Example Scripts (preserve extensions)
- Bash (lowercase filenames):
bash
#!/bin/bash shopt -s nullglob for f in; do [ -f “\(f</span><span class="token" style="color: rgb(163, 21, 21);">"</span><span> </span><span class="token" style="color: rgb(57, 58, 52);">]</span><span> </span><span class="token" style="color: rgb(57, 58, 52);">||</span><span> </span><span class="token builtin" style="color: rgb(43, 145, 175);">continue</span><span> </span><span> </span><span class="token assign-left" style="color: rgb(54, 172, 170);">ext</span><span class="token" style="color: rgb(57, 58, 52);">=</span><span class="token" style="color: rgb(163, 21, 21);">"</span><span class="token" style="color: rgb(54, 172, 170);">\){f##.}” name=“${f%.}” new=“\((</span><span class="token builtin" style="color: rgb(43, 145, 175);">echo</span><span class="token" style="color: rgb(54, 172, 170);"> </span><span class="token" style="color: rgb(54, 172, 170);">"</span><span class="token" style="color: rgb(54, 172, 170);">\)name” | tr ’[:upper:]’ ’[:lower:]’).\({ext</span><span class="token" style="color: rgb(57, 58, 52);">,,</span><span class="token" style="color: rgb(54, 172, 170);">}</span><span class="token" style="color: rgb(163, 21, 21);">"</span><span> </span><span> </span><span class="token" style="color: rgb(0, 0, 255);">if</span><span> </span><span class="token" style="color: rgb(57, 58, 52);">[</span><span> </span><span class="token" style="color: rgb(163, 21, 21);">"</span><span class="token" style="color: rgb(54, 172, 170);">\)f“ != ”\(new</span><span class="token" style="color: rgb(163, 21, 21);">"</span><span> </span><span class="token" style="color: rgb(57, 58, 52);">]</span><span class="token" style="color: rgb(57, 58, 52);">;</span><span> </span><span class="token" style="color: rgb(0, 0, 255);">then</span><span> </span><span class="token" style="color: rgb(57, 58, 52);">mv</span><span> -i -- </span><span class="token" style="color: rgb(163, 21, 21);">"</span><span class="token" style="color: rgb(54, 172, 170);">\)f“ ”\(new</span><span class="token" style="color: rgb(163, 21, 21);">"</span><span class="token" style="color: rgb(57, 58, 52);">;</span><span> </span><span class="token" style="color: rgb(0, 0, 255);">fi</span><span> </span><span></span><span class="token" style="color: rgb(0, 0, 255);">done</span><span> </span></code></div></div></pre> <ul> <li>PowerShell (convert to snake_case):</li> </ul> <pre><div class="XG2rBS5V967VhGTCEN1k"><div class="nHykNMmtaaTJMjgzStID"><div class="HsT0RHFbNELC00WicOi8"><i><svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M15.434 7.51c.137.137.212.311.212.49a.694.694 0 0 1-.212.5l-3.54 3.5a.893.893 0 0 1-.277.18 1.024 1.024 0 0 1-.684.038.945.945 0 0 1-.302-.148.787.787 0 0 1-.213-.234.652.652 0 0 1-.045-.58.74.74 0 0 1 .175-.256l3.045-3-3.045-3a.69.69 0 0 1-.22-.55.723.723 0 0 1 .303-.52 1 1 0 0 1 .648-.186.962.962 0 0 1 .614.256l3.541 3.51Zm-12.281 0A.695.695 0 0 0 2.94 8a.694.694 0 0 0 .213.5l3.54 3.5a.893.893 0 0 0 .277.18 1.024 1.024 0 0 0 .684.038.945.945 0 0 0 .302-.148.788.788 0 0 0 .213-.234.651.651 0 0 0 .045-.58.74.74 0 0 0-.175-.256L4.994 8l3.045-3a.69.69 0 0 0 .22-.55.723.723 0 0 0-.303-.52 1 1 0 0 0-.648-.186.962.962 0 0 0-.615.256l-3.54 3.51Z"></path></svg></i><p class="li3asHIMe05JPmtJCytG wZ4JdaHxSAhGy1HoNVja cPy9QU4brI7VQXFNPEvF">powershell</p></div><div class="CF2lgtGWtYUYmTULoX44"><button type="button" class="st68fcLUUT0dNcuLLB2_ ffON2NH02oMAcqyoh2UU MQCbz04ET5EljRmK3YpQ CPXAhl7VTkj2dHDyAYAf" data-copycode="true" role="button" aria-label="Copy Code"><svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M9.975 1h.09a3.2 3.2 0 0 1 3.202 3.201v1.924a.754.754 0 0 1-.017.16l1.23 1.353A2 2 0 0 1 15 8.983V14a2 2 0 0 1-2 2H8a2 2 0 0 1-1.733-1H4.183a3.201 3.201 0 0 1-3.2-3.201V4.201a3.2 3.2 0 0 1 3.04-3.197A1.25 1.25 0 0 1 5.25 0h3.5c.604 0 1.109.43 1.225 1ZM4.249 2.5h-.066a1.7 1.7 0 0 0-1.7 1.701v7.598c0 .94.761 1.701 1.7 1.701H6V7a2 2 0 0 1 2-2h3.197c.195 0 .387.028.57.083v-.882A1.7 1.7 0 0 0 10.066 2.5H9.75c-.228.304-.591.5-1 .5h-3.5c-.41 0-.772-.196-1-.5ZM5 1.75v-.5A.25.25 0 0 1 5.25 1h3.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-3.5A.25.25 0 0 1 5 1.75ZM7.5 7a.5.5 0 0 1 .5-.5h3V9a1 1 0 0 0 1 1h1.5v4a.5.5 0 0 1-.5.5H8a.5.5 0 0 1-.5-.5V7Zm6 2v-.017a.5.5 0 0 0-.13-.336L12 7.14V9h1.5Z"></path></svg>Copy Code</button><button type="button" class="st68fcLUUT0dNcuLLB2_ WtfzoAXPoZC2mMqcexgL ffON2NH02oMAcqyoh2UU MQCbz04ET5EljRmK3YpQ GnLX_jUB3Jn3idluie7R"><svg fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" fill-rule="evenodd" d="M20.618 4.214a1 1 0 0 1 .168 1.404l-11 14a1 1 0 0 1-1.554.022l-5-6a1 1 0 0 1 1.536-1.28l4.21 5.05L19.213 4.382a1 1 0 0 1 1.404-.168Z" clip-rule="evenodd"></path></svg>Copied</button></div></div><div class="mtDfw7oSa1WexjXyzs9y" style="color: var(--sds-color-text-01); font-family: var(--sds-font-family-monospace); direction: ltr; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; font-size: var(--sds-font-size-label); line-height: 1.2em; tab-size: 4; hyphens: none; padding: var(--sds-space-x02, 8px) var(--sds-space-x04, 16px) var(--sds-space-x04, 16px); margin: 0px; overflow: auto; border: none; background: transparent;"><code class="language-powershell" style="color: rgb(57, 58, 52); font-family: Consolas, "Bitstream Vera Sans Mono", "Courier New", Courier, monospace; direction: ltr; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; font-size: 0.9em; line-height: 1.2em; tab-size: 4; hyphens: none;"><span class="token" style="color: rgb(57, 58, 52);">Get-ChildItem</span><span> </span><span class="token" style="color: rgb(57, 58, 52);">-</span><span>File </span><span class="token" style="color: rgb(57, 58, 52);">|</span><span> </span><span class="token" style="color: rgb(57, 58, 52);">ForEach-Object</span><span> </span><span class="token" style="color: rgb(57, 58, 52);">{</span><span> </span><span> </span><span class="token" style="color: rgb(54, 172, 170);">\)ext = \(_</span><span class="token" style="color: rgb(57, 58, 52);">.</span><span>Extension </span><span> </span><span class="token" style="color: rgb(54, 172, 170);">\)name = \(_</span><span class="token" style="color: rgb(57, 58, 52);">.</span><span>BaseName </span><span> </span><span class="token" style="color: rgb(54, 172, 170);">\)new = (\(name</span><span> </span><span class="token" style="color: rgb(57, 58, 52);">-replace</span><span> </span><span class="token" style="color: rgb(163, 21, 21);">'[^\w]+'</span><span class="token" style="color: rgb(57, 58, 52);">,</span><span class="token" style="color: rgb(163, 21, 21);">'_'</span><span class="token" style="color: rgb(57, 58, 52);">)</span><span class="token" style="color: rgb(57, 58, 52);">.</span><span>ToLower</span><span class="token" style="color: rgb(57, 58, 52);">(</span><span class="token" style="color: rgb(57, 58, 52);">)</span><span> </span><span class="token" style="color: rgb(57, 58, 52);">+</span><span> </span><span class="token" style="color: rgb(54, 172, 170);">\)ext if (\(_</span><span class="token" style="color: rgb(57, 58, 52);">.</span><span>Name </span><span class="token" style="color: rgb(57, 58, 52);">-ne</span><span> </span><span class="token" style="color: rgb(54, 172, 170);">\)new) { Rename-Item -LiteralPath \(_</span><span class="token" style="color: rgb(57, 58, 52);">.</span><span>FullName </span><span class="token" style="color: rgb(57, 58, 52);">-</span><span>NewName </span><span class="token" style="color: rgb(54, 172, 170);">\)new -WhatIf } }
(remove -WhatIf to execute)
- Python (title case, handle Unicode):
python
import os, unicodedata def title_case(s): return ’ ‘.join(w.capitalize() for w in s.split()) for fname in os.listdir(’.’): if os.path.isfile(fname): name, ext = os.path.splitext(fname) new = title_case(name) + ext if new != fname: os.rename(fname, new)
Best Practices
- Always test first: Run with dry-run flags (e.g., -WhatIf) or print proposed changes before renaming.
- Preserve extensions: Treat the extension separately to avoid breaking file types.
- Handle collisions: Detect existing target names and decide to skip, append suffixes, or prompt.
- Backup or use version control: Keep a backup or operate in a git repo so changes can be reverted.
- Respect file metadata: Renaming typically preserves timestamps, but check if your tool alters metadata.
- Unicode & normalization: Normalize filenames (NFC/NFD) when moving between macOS and Linux to avoid duplicates.
- Permissions & ownership: Run with appropriate permissions; avoid running mass-rename as root unless necessary.
- Recursive options: Use recursive renaming carefully; exclude system directories and hidden files unless intended.
Tips for Specific Cases
- Convert to snake_case: replace non-alphanumeric with underscores, collapse multiple underscores, trim edges.
- Convert camelCase to words: insert separators before capital letters then lowercase.
- Keep leading dots (hidden files) intact when operating on Unix-like systems.
- For mixed-case preservation (e.g., acronyms), create rule sets or mapping tables.
Quick Workflow (recommended)
- Scan: List files and show proposed new names.
- Dry-run: Execute script with dry-run flag or print-only mode.
- Backup: Commit to git or copy files to backup folder.
- Execute: Perform renaming.
- Verify: Check for collisions, broken references, or issues.
If you want, I can generate a ready-to-run script tailored to your OS and desired naming convention.
Leave a Reply