You’ve got a directory full of files with spaces and weird characters in the names. Maybe they came from a Windows box. Maybe someone named them like they were writing a Victorian novel. Either way, you need them cleaned up before you can use them in a script without losing your mind to quote escaping hell.
Here’s the thing: shell filenames with spaces are the enemy of automation. They break loops, they break pipes, they break your soul at 2 AM when a script silently fails because for file in * split on the space instead of the newline.
Let’s fix it.
Replace spaces with underscores
The simplest case. You want my file.txt to become my_file.txt.
Using find with rename:
find /tmp/ -depth -name "* *" -execdir rename 's/ /_/g' "{}" \;This finds all files with spaces in /tmp/, then uses the rename command (Perl-based, available on most Linux systems) to replace each space with an underscore. The -depth flag makes sure subdirectories are processed before their parent, so renaming doesn’t mess up the directory tree.
If you prefer a shell loop instead:
for file in *; do mv "$file" "${file// /_}"doneThat ${file// /_} is bash parameter expansion—replace all spaces (the first //) with underscores (the second part after the slash). Dead simple, no external commands needed.
Replace spaces with hyphens
Some people prefer hyphens to underscores. Both work; pick your poison.
Using find:
find /tmp/ -depth -name "* *" -execdir rename 's/ /-/g' "{}" \;With a shell loop:
for file in *; do mv "$file" "${file// /-}"doneSame deal—parameter expansion, just with a hyphen instead of an underscore.
Remove spaces completely
Nuke them entirely.
find /tmp/ -depth -name "* *" -execdir rename 's/ //g' "{}" \;Be careful with this one. It’ll mangle things like my file.txt into myfile.txt, which is fine, but if you’ve got my - file.txt it becomes my-file.txt… actually that works out. Still, test on a copy first.
Remove special characters
Sometimes it’s not just spaces—you’ve got apostrophes, parentheses, hyphens in weird places, all kinds of nonsense.
Here’s a loop that strips out punctuation entirely:
for file in *.mp3; do mv "$file" "${file//[[:punct:]]/}"doneThe [[:punct:]] character class matches anything that’s not alphanumeric or whitespace. WARNING: This will also eat the dot in your file extension (.mp3 becomes mp3). You probably don’t want that. Better approach:
for file in *.mp3; do name="${file%.*}" # filename without extension ext="${file##*.}" # just the extension clean="${name//[[:punct:]]/}" # strip punctuation from name only mv "$file" "${clean}.${ext}"doneNow you’re only stripping punctuation from the filename part, leaving the extension alone.
Use rename for find-and-replace patterns
The rename command is a Perl-based tool that’s incredibly powerful for bulk renaming. It’s much safer than building shell loops because it does a dry-run by default (with the -n flag) so you can see what it’s going to do before it does it.
Remove underscores:
rename -n 's/_//g' *(Add -n to see what it’ll do without actually renaming. Remove -n to commit the changes.)
Remove a specific substring:
rename 's/2012mp3/2012/' *.mp3Replace a dash in the middle of a filename:
rename 's/1234/12-34/' *.mp3Remove the first N characters:
for file in *.mp3; do temp="${file:1}" # skip first character (use 2 for two chars, etc.) mv "$file" "$temp"doneUsing perl for advanced patterns
If you need something more powerful, Perl gives you the full regex arsenal:
ls *.bed | perl -ne 'use File::Copy; chomp; $old=$_; s/\s+/_/g; move($old,$_);'This reads filenames from ls, replaces any whitespace (including multiple spaces) with a single underscore, and moves the file. Perl’s s/\s+/_/g is more flexible than bash’s ${var// /} because \s+ matches any consecutive whitespace, not just single spaces.
The safe way: always test first
Before you unleash a batch rename on a production directory:
# Create a test directorymkdir test_dircp my_files/* test_dir/
# Run your rename command therecd test_dirfor file in *; do mv "$file" "${file// /_}"done
# Look at the resultsls -la
# If it looks good, do it for realcd ..for file in my_files/*; do mv "$file" "${file// /_}"doneIt’s tedious, but it beats explaining to your team why all the filenames got corrupted.
Pick your tool
renamecommand: Best for complex patterns, has a dry-run mode (-n), works recursively.- Bash parameter expansion (
${var//old/new}): Fastest, no dependencies, good for simple replacements. find + rename: Best for deep directory trees, the-depthflag prevents path issues.- Perl: When you need regex superpowers and character classes like
\s+or[[:punct:]].
Whichever you pick, your scripts will thank you when they don’t have to dance around filenames with spaces anymore.