Replace spaces with underscores in filenames (shell script)

By

Learn how to recursively replace spaces with underscores in every filename in a folder, with a small Fish shell script that combines find, tr, and mv.

~~~

To replace spaces with underscores in every filename inside a folder, recursively, you can combine find, tr and mv in a small shell script.

I had to do exactly this while working on my website. It’s one of those one-off operations, and I don’t really want to search, install (or buy) an app just to do that.

Here’s the Fish Shell script I used:

#!/opt/homebrew/bin/fish

# define the directory to search
set search_dir ./

# recursively find all files in the search directory
find $search_dir -type f | while read -l file
    # replace spaces with underscores in the file name
    set new_name (echo $file | tr ' ' '_')

    # rename the file
    mv $file $new_name
end

I put this in the folder containing all the files and folders I wanted to rename, named replace_spaces.fish.

Then I set it as executable using chmod +x replace_spaces.fish and finally ran it using ./replace_spaces.fish.

How the script works

find $search_dir -type f prints the path of every file under the folder, one per line. The -type f flag limits the results to files, skipping directories.

The while read -l file loop reads those paths one at a time into a local variable called file.

tr ' ' '_' translates every space character into an underscore. So ./photos/beach sunset.jpg becomes ./photos/beach_sunset.jpg.

Finally, mv renames the file from the old path to the new one. Files that contain no spaces get “renamed” to themselves, which is harmless but noisy, since mv complains about it.

Watch out for folders with spaces

There’s one case where this script fails: files inside a directory that itself contains spaces.

Say you have ./my docs/old file.txt. The script computes ./my_docs/old_file.txt as the target, but the my_docs folder doesn’t exist, so mv fails:

mv: rename ./my docs/old file.txt to ./my_docs/old_file.txt: No such file or directory

The fix is to rename the folders first, so every file already lives in a path without spaces. Since it was a handful of folders in my case, I did it by hand.

You can also make the script quieter by skipping files that don’t need renaming:

find $search_dir -type f | while read -l file
    set new_name (echo $file | tr ' ' '_')

    if test "$file" != "$new_name"
        mv $file $new_name
    end
end

Now mv only runs when the name actually changes.

One last tip: run the script on a copy of the folder first, or print the old and new names with echo before you let mv loose. Bulk renames are hard to undo.

Tagged: CLI · All topics
~~~

Related posts about cli: