How to replace one char with another in all filenames of the current directories?

125,821

Solution 1

If you need to rename files in subdirectories as well, and your find supports the -execdir predicate, then you can do

find /search/path -depth -name '* *' \
    -execdir bash -c 'mv -- "$1" "${1// /_}"' bash {} \;

Thank to @glenn jackman for suggesting -depth option for find and to make me think.

Note that on some systems (including GNU/Linux ones), find may fail to find files whose name contains spaces and also sequences of bytes that don't form valid characters (typical with media files with names with non-ASCII characters encoded in a charset different from the locale's). Setting the locale to C (as in LC_ALL=C find...) would address the problem.

Solution 2

In any shell, you can loop over the files whose name contains a space. Replacing the spaces with underscores is easy in bash, ksh and zsh with the ${VARIABLE//PATTERN/REPLACEMENT} construct.

for x in *" "*; do
  mv -- "$x" "${x// /_}"
done

On Debian, Ubuntu and derivatives, you can use the Perl rename (other distributions ship a different program as rename, and that program isn't helpful here).

rename 's/ /_/g' *

An obligatory zsh solution:

autoload zmv
zmv '(*)' '${1// /_}'

An obligatory POSIX solution:

for x in *" "*; do
  y=$(printf %sa "$x" | tr " " "_")
  mv -- "$x" "${y%a}"
done

Solution 3

You can use rename for this (here assuming the one from util-linux, not the perl one):

cd /path/to/dir
rename ' ' _ *\ *

This will find all files and directories space in the name and replace the space with an underscore. Since it uses glob file matching you need to be in the right directory to start with.

If you want to do recursive matches you can, but you might have to execute the rename a couple times to catch any items in directories that themselves got renamed:

cd /path/to/dir
shopt -s globstar
rename ' ' _ **/*\ *
!!; !!

Solution 4

Another option would be mmv, if installed.

mmv \*\ \* \#1_#2

Solution 5

On Debian/Ubuntu, building upon the answers of Caleb and Gilles, this is what worked for me to rename files recursively:

cd /path/to/dir
shopt -s globstar
rename 's/ /_/g' **

Note: To preview what files would be renamed and how, use the -n switch with rename:

rename -n 's/ /_/g' **

Another note: setting globstar makes ** match files in all subdirectories, so if only current directory is desired, don't set globstar or use * instead of **.

One more note: The rename command needs to be run more than once for files with multiple occurrences of the search term.

Share:
125,821
NobbZ
Author by

NobbZ

There is not much to say... I am just me :)

Updated on September 18, 2022

Comments

  • NobbZ
    NobbZ over 1 year

    How do you rename all files/subdirs in the current folder?

    Lets say, I have many files and subdirs that are with spaces and I want to replace all the spaces with an underscore.

    File 1
    File 2
    File 3
    Dir 1
    Dir 3
    

    should be renamed to

    File_1
    File_2
    File_3
    Dir_1
    Dir_3
    
  • tcoolspy
    tcoolspy over 12 years
    You would need to use find -maxdepth 1 to do exactly what the OP asked about operating on the current folder.
  • Angel Todorov
    Angel Todorov over 12 years
    Use find's -depth option, and you can get rid of the sort.
  • enzotib
    enzotib over 12 years
    @glenn jackman: thank you, I now understand my solution was wrong.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 12 years
    rename will rename the specified files by replacing the first occurrence of from in their name by to.” So this will only work for files with a single space in their name. (You could call rename in a loop, but it's not really the right tool here.)
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 12 years
    This only changes the first space into a _, it doesn't work names with multiple spaces.
  • glglgl
    glglgl over 12 years
    Oups, you are right. Sorry for the confusion.
  • kroiz
    kroiz over 11 years
    Could some one please elaborate on what should be change so this could be used to replace other characters. For example I could not make it work for replacing _ with a period (.)
  • enzotib
    enzotib over 11 years
    Change -name '* *' to -name '*_*' and change "${1// /_}" to "${1//_/.}"
  • kroiz
    kroiz over 11 years
    Thanks @enzotib, I want to google for help about this "${1// /_}". What is it called?
  • enzotib
    enzotib over 11 years
    @kroiz: it is called "Pattern substitution". You could find it in bash's man page.
  • Stéphane Chazelas
    Stéphane Chazelas over 10 years
    That only works if directories don't contain spaces. (a rename of a b/c d to a_b/c_d wouldn't work, you'd need first to rename a b/c d to a b/c_d, and then a b to a_b).
  • Markus Pscheidt
    Markus Pscheidt over 10 years
    @Stephane: Does it mean to run the same rename command twice?
  • Stéphane Chazelas
    Stéphane Chazelas over 10 years
    Well, more like as many times as there are nested levels of directories with spaces. Ideally, you want to traverse the directory depth first, and convert only the basename of the file like in the accepted solution. Also note that bash's ** excludes dotfiles and traverses symlinks.
  • JulianLai
    JulianLai over 5 years
    What does the 'g' at the end of the "rename" command mean? I didn't see it in the manual.
  • JulianLai
    JulianLai over 5 years
    How to rename directory only? I don't want to change the filename.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 5 years
    @JulianLai s/…/…/g means to replace all occurrences. It's not very well explained in the manual. If you want to rename directories, there are ways, please search for it (I think I've seen it before) and if you can't find it ask a new question.
  • Abinash Dash
    Abinash Dash almost 5 years
    great ... this helped me to rename all _MG_blah.JPG to IMG_blah.JPG .
  • benba
    benba over 4 years
    @enzotib: What's the purpose of the second bash command?
  • enzotib
    enzotib over 4 years
    @benba: it is not a second bash command, you should interpret it as: bash -c script-string $0 $1 $2 etc, so the second time the string bash appears, it is in the $0 position, and so gives a name to the script.
  • benba
    benba over 4 years
    @enzotib: got it thanks
  • Admin
    Admin almost 4 years
    What is -- for in mv command?
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' almost 4 years
    @0x476f72616e So that if $x starts with a - it isn't interpreted as an option.
  • Matthew K.
    Matthew K. about 3 years
    To recursively replace every space in every file name use: find dirpath/ -depth -type f -exec rename 's/ /_/g' {} \; where to do this to only directory names replace -type f with -type d while for BOTH file names and directory names, remove the -type flag. If only replacing file names then -depth is optional. You may need to download the rename command: sudo apt install rename. Credit for solution: unix.stackexchange.com/a/282364/380315
  • Jeff Schaller
    Jeff Schaller about 3 years
    This is really similar to an existing answer.
  • Admin
    Admin almost 2 years
    rename has a -a parameter that tells it to replace all occurrences