32

I tried following shell script which should replace spaces from all xml filenames

for xml_file in $(find $1 -name "* .xml" -type f);
do
 echo "removing spaces from XML file:" $xml_file
 mv "$xml_file" "${xml_file// /_}";
done

Suppose, I have xml file with the name xy z.xml, then it gives:

removing spaces from XML file: /home/krishna/test/xy
mv: cannot stat `/home/krishna/test/xy': No such file or directory
removing spaces from XML file: .xml
mv: cannot stat `z.xml': No such file or directory
0

4 Answers 4

45

Use this with bash:

find $1 -name "* *.xml" -type f -print0 | \
  while read -d $'\0' f; do mv -v "$f" "${f// /_}"; done

find will search for files with a space in the name. The filenames will be printed with a nullbyte (-print0) as delimiter to also cope with special filenames. Then the read builtin reads the filenames delimited by the nullbyte and finally mv replaces the spaces with an underscore.

EDIT: If you want to remove the spaces in the directories too, it's a bit more complicated. The directories are renamed and then not anymore accessible by the name find finds. Try this:

find -name "* *" -print0 | sort -rz | \
  while read -d $'\0' f; do mv -v "$f" "$(dirname "$f")/$(basename "${f// /_}")"; done

The sort -rz reverses the file order, so that the deepest files in a folder are the first to move and the folder itself will be the last one. So, there are never folders renamed before all files and folder are rename inside of it. The mv command in the loop is a bit changed too. In the target name, we only remove the spaces in the basename of the file, else it wouldn't be accessible.

6
  • I am trying to replace spaces with underscore in all directory but it is giving error because after changing name that directory is not accessible.
    – krishna
    Aug 14, 2015 at 11:42
  • @krishna I added an edit to my answer.
    – chaos
    Aug 14, 2015 at 12:07
  • 1
    @chaos wow, just ran these two on two systems and it worked like a charm, few files and dirs not going through but it's only a few Aug 14, 2018 at 5:46
  • 2
    On macOS we need to specify the directory in the find command before the -name option find . -name "* *" -print0 | sort -rz | \ while read -d $'\0' f; do mv -v "$f" "$(dirname "$f")/$(basename "${f// /_}")"; done Oct 20, 2019 at 6:47
  • Could this solution applied to remove a specific patterns from the file name like -_-?
    – alper
    Jul 2, 2022 at 12:48
32
  1. Using rename

    find . -type f -name "* *.xml" -exec rename "s/\s/_/g" {} \;
    

    or with $1

    find "$1" -type f -name "* *.xml" -exec rename "s/\s/_/g" {} \;
    
  2. Using mv

    find . -type f -name "* *.xml" -exec bash -c 'mv "$0" "${0// /_}"' {} \;
    

    or with $1

    find "$1" -type f -name "* *.xml" -exec bash -c 'mv "$0" "${0// /_}"' {} \;
    
3
  • I don't know why but your given command is not working for me. It is not showing any error and output.
    – krishna
    Aug 14, 2015 at 10:28
  • @krishna corrected, sorry
    – A.B.
    Aug 14, 2015 at 10:49
  • 1
    Rename worked for me. Fistbump! Sep 12, 2019 at 18:38
7

This is a method I found while facing the same problem:

for f in *; do mv "$f" `echo $f | tr ' ' '_'`; done

I was writing a bash script file to automatically update my ssl certificates.

7

Use rename:

rename 's/\s/_/g' ./*.xml

No need for find :)

2
  • 1
    add the trailing g to the regex and it works perfectly. 's/\s/_/g'
    – jbrahy
    Nov 5, 2019 at 19:00
  • Good tip, thanks :) Nov 5, 2019 at 20:33

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .