find . -depth -name ' *' -exec sh -c '
for pathname do
newname=${pathname##*/}
newname=${newname#"${newname%%[! ]*}"}
newpathname=${pathname%/*}/$newname
if [ -z "$newname" ] || [ -e "$newpathname" ]; then
continue
fi
mv -v "$pathname" "$newpathname"
done' sh {} +
The above finds any file or directory whose name starts with at least one space character in or below the current directory. It does this in a depth-first order (due to -depth) to avoid renaming directories that it hasn't yet processed the contents of; renaming a directory would otherwise cause find not to find it later, as it was renamed.
A short in-line shell script is called for batches of names that start with a space. The script iterates over the given pathnames and starts by extracting the actual name from the end of the current pathname into the variable newname. It does this using the standard parameter substitution, ${pathname##*/}, removing everything to the last / in the string (the longest prefix matching */), leaving the final pathname component. This is essentially the same as "$(basename "$pathname")" in this case.
We then need to trim off the spaces guaranteed to exist at the start of the string in $newname. We do this by first removing everything but the spaces with ${newname%%[! ]*} (the longest suffix matching [! ]*, i.e. from the first non-space character onwards), and then removing the result of that from the start of the $newname string with ${newname#"${newname%%[! ]*}"}.
The destination path in the mv command is made up of the directory path of $pathname concatenated by a slash and the new name, i.e. ${pathname%/*}/$newname, which is essentially the same as "$(dirname "$pathname")/$newname".
The code detects name collisions and silently skips the processing of names that would collide. It also skips names that collapse to empty strings. This is what the if statement before mv does. If you want to bring these names to the user's attention, then do so before continue.
Test run on a copy of your backed-up data.
Test-running the code above:
$ tree -Q
"."
|-- " dir0"
| |-- " o dir1"
| | |-- " otherfile"
| | `-- "dir2"
| | |-- " dir3"
| | `-- " moar"
| `-- "file"
`-- "script"
4 directories, 4 files
$ sh script
./ dir0/ o dir1/dir2/ moar -> ./ dir0/ o dir1/dir2/moar
./ dir0/ o dir1/dir2/ dir3 -> ./ dir0/ o dir1/dir2/dir3
./ dir0/ o dir1/ otherfile -> ./ dir0/ o dir1/otherfile
./ dir0/ o dir1 -> ./ dir0/o dir1
./ dir0 -> ./dir0
Notice how it starts at the bottom of the directory hierarchy. If it had tried renaming dir0 first, it would have failed to enter it later to rename the other directories and files.
$ tree -Q
"."
|-- "dir0"
| |-- "file"
| `-- "o dir1"
| |-- "dir2"
| | |-- "dir3"
| | `-- "moar"
| `-- "otherfile"
`-- "script"
4 directories, 4 files