How to replace spaces in all file names with underscore in Linux using shell script?

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

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

Use this with bash:

find $1 -name "* *.xml" -type f -print0 | 
  while read -d $'' 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 $'' 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.

Method 2

  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// /_}"' {} ;

Method 3

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.

Method 4

Use rename:

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

No need for find 🙂


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x