1

I have the following script, that will replace all spaces in files and directories recursively:

################### SETUP VARIABLES #######################
number=0                    # Number of renamed.
number_not=0                # Number of not renamed.
IFS=$'\n'
array=( `find ./ -type d` ) # Find catalogs recursively.


######################## GO ###############################
# Reverse cycle.
for (( i = ${#array[@]}; i; )); do
     # Go in to catalog.
     pushd "${array[--i]}" >/dev/null 2>&1
     # Search of all files in the current directory.
     for name in *
     do
             # Check for spaces in names of files and directories.
             echo "$name" | grep -q " "
             if [ $? -eq 0 ]
             then
                # Replacing spaces with underscores.
                newname=`echo $name | sed -e "s/ /_/g"`
                if [ -e $newname ]
                then
                        let "number_not +=1"
                        echo " Not renaming: $name"
                else
                        # Plus one to number.
                        let "number += 1"
                        # Message about rename.
                        echo "$number Renaming: $name"
                        # Rename.
                        mv "$name" "$newname"
                fi
             fi
     done
     # Go back.
     popd >/dev/null 2>&1
done

echo -en "\n All operations is complited."

if [ "$number_not" -ne "0" ]
  then echo -en "\n $number_not not renamed."
fi

if [ "$number" -eq "0" ]
  then echo -en "\n Nothing been renamed.\n"
elif [ "$number" -eq "1" ]
   then echo -en "\n $number renamed.\n"
   else echo -en "\n Renamed files and catalogs: $number\n"
fi

exit 0

It works by populating an array with directories:

array=( `find ./ -type d` ) # Find catalogs recursively.

If I want to force this script to work from a specific directory, could I do something like this?

array=( `find /my/start/directory/ -type d` ) # Find catalogs recursively.

I'm asking here (instead of just running it) as I want to double check it's correct, I don't want to rename every file on the server by accident!

  • The script, as it's currently written, tries to replace spaces in filenames with underscores, but the result of the find in the array will be split on spaces, so it will break if there are directories with spaces in their names. – Kusalananda Jul 5 '18 at 15:42
  • Sorry, I missed that you set IFS to a newline. Still, there are easier ways to do it. – Kusalananda Jul 5 '18 at 15:59
2

You could test your script with the change that you proposed by commenting out the mv command and running it. There are a few too many things going on in the script for me to immediately say that it would be ok, but if the current script works (you obviously have no directory names with newlines in them, or the array array would be broken, or names starting with dashes which would potentially confuse mv and echo in a few places) then I'd hazard to guess it would be ok.


To replace spaces with underscores in the filenames of both directories and other files recursively:

topdir=.
find "$topdir" -depth -name "* *" -exec bash -c '
    for pathname do
        # $pathname will have at least one space in it
        newname=${pathname##*/}  # get basename
        newname=${newname// /_}  # replace spaces with underscores
        printf "Would move %s to %s\n" "$pathname" "${pathname%/*}/$newname"
        # mv "$pathname" "${pathname%/*}/$newname"
     done' bash {} +

This would find anything in or under $topdir that contained at least one space in their name. It would collect these pathnames and give them to an in-line bash script. The script would extract the filename portion of each pathname and replace the spaces with underscores. The actual mv operation is commented out for safety.

The -depth option is necessary here since we don't want to rename directories that we haven't yet visited. With it, find will do a depth-first traversal of the directory hierarchy.

Parameter substitutions used:

  • ${variable##*/}: removes everything before the last slash in the value of variable. More or less the same as $( basename "$variable" ).
  • ${variable%/*}: removes everything after the last slash. More or less the same as $( dirname "$variable" ).
  • ${variable//pattern/replacement}: Replaces everything matching pattern with replacement in the value of variable (this is a bash extension)

No check is made for whether the new filename already exists or not. This could easily be done in the internal bash script with e.g.

if [ -e "${pathname%/*}/$newname" ]; then
    printf "Will not rename %s, new name exists\n" "$pathname" >&2
else
    printf "Would move %s to %s\n" "$pathname" "${pathname%/*}/$newname"
    # mv "$pathname" "${pathname%/*}/$newname"
fi

Testing:

$ tree
.
|-- a directory
|   `-- yet another file
|-- script.sh
|-- some file
`-- some other file

1 directory, 4 files

$ sh script.sh
Would move ./some file to ./some_file
Would move ./some other file to ./some_other_file
Would move ./a directory/yet another file to ./a directory/yet_another_file
Would move ./a directory to ./a_directory

Related:

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.