Skip to content
Go back

Bulk rename files in bash

Updated:
By SumGuy 5 min read
Bulk rename files in bash

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:

replace_spaces_underscore.sh
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:

loop_underscore.sh
for file in *; do
mv "$file" "${file// /_}"
done

That ${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:

replace_spaces_hyphen.sh
find /tmp/ -depth -name "* *" -execdir rename 's/ /-/g' "{}" \;

With a shell loop:

loop_hyphen.sh
for file in *; do
mv "$file" "${file// /-}"
done

Same deal—parameter expansion, just with a hyphen instead of an underscore.

Remove spaces completely

Nuke them entirely.

remove_spaces.sh
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:

remove_punct.sh
for file in *.mp3; do
mv "$file" "${file//[[:punct:]]/}"
done

The [[: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:

remove_punct_safe.sh
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}"
done

Now 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_remove_underscores.sh
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_remove_substring.sh
rename 's/2012mp3/2012/' *.mp3

Replace a dash in the middle of a filename:

rename_replace_dash.sh
rename 's/1234/12-34/' *.mp3

Remove the first N characters:

rename_trim_prefix.sh
for file in *.mp3; do
temp="${file:1}" # skip first character (use 2 for two chars, etc.)
mv "$file" "$temp"
done

Using perl for advanced patterns

If you need something more powerful, Perl gives you the full regex arsenal:

perl_bulk_rename.sh
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:

test_rename.sh
# Create a test directory
mkdir test_dir
cp my_files/* test_dir/
# Run your rename command there
cd test_dir
for file in *; do
mv "$file" "${file// /_}"
done
# Look at the results
ls -la
# If it looks good, do it for real
cd ..
for file in my_files/*; do
mv "$file" "${file// /_}"
done

It’s tedious, but it beats explaining to your team why all the filenames got corrupted.

Pick your tool

Whichever you pick, your scripts will thank you when they don’t have to dance around filenames with spaces anymore.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Previous Post
Remove all old installed but unused kernels
Next Post
When systemd swallows your service logs

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts