I tried following shell script to remove all spaces with underscores:

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

If I have a directory /home/user/g h/y h/u j/ it will modify y h directory to y_h and then it will give an error for /home/user/g h/y h/u j:

No such file or directory

Use this:

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

find will search for files and folders with a space in the name. This will be printed (-print0) with nullbytes as delimiters to cope with special filenames too.

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.

Finally, the mv command renames the file/folder. In the target name, we only remove the spaces of the files basename, else it wouldn't be accessible anymore.

| improve this answer | |
find $1 -depth -name "* *" -type d -execdir rename 's/ /_/g' "{}" \;
| improve this answer | |

separate between filename basename (i.e. last name in the path) and dirname:

find $1 -depth -name "* *" -print0 | \
while read -d $'\0' f ; do
    a="$(dirname "$f")"
    b="$(basename "$f")"
    #optional check if the basename changes -> reduces errors in mv command
    #only needed when using -wholename instead of -name in find, so skippable
    if [ "${b// /_}" != "$b" ] ; then
      mv -v "$a"/"$b"  "$a"/"${b// /_}"

The problem of first changing subdirs before changing higher dirs is already taken care of with the -depth specification in find.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.