I'm trying to recursively rename some files with parent folders that contain spaces, I've tried the following command in ubuntu terminal:
find . -type f -name '* *' -print0 | xargs -0 rename 's/ //'
It has given out the following error refering to the folder names:
Can't rename ./FOLDER WITH SPACES/FOLDER1.1/SUBFOLDER1.1/FILE.01 A.jpg
./FOLDERWITH SPACES/FOLDER1.1/SUBFOLDER1.1/FILE.01 A.jpg: No such file
or directory
If i'm not mistaken the fact that the folders have white spaces in them shouldn't affect the process since it uses the flag -f.
CodePudding user response:
What is passed to xargs is the full path of the file, not just the file name. So your s/ // substitute command also removes spaces from the directory part. And as the new directories (without spaces) don't exist you get the error you see. The renaming, in your example, was:
./FOLDER WITH SPACES/FOLDER1.1/SUBFOLDER1.1/FILE.01 A.jpg ->
./FOLDERWITH SPACES/FOLDER1.1/SUBFOLDER1.1/FILE.01 A.jpg
And this is not possible if directories ./FOLDERWITH SPACES/FOLDER1.1/SUBFOLDER1.1 don't already exist.
Try with the -d option of rename:
find . -type f -name '* *' -print0 | xargs -0 rename -d 's/ //'
(the -d option only renames the filename component of the path.)
Note that you don't need xargs. You could use the -execdir action of find:
find . -type f -name '* *' -execdir rename 's/ //' {}
And as the -execdir command is executed in the subdirectory containing the matched file, you don't need the -d option of rename any more. And the -print0 action of find is not needed neither.
Last note: if you want to replace all spaces in the file names, not just the first one, do not forget to add the g flag: rename 's/ //g'.
CodePudding user response:
You're correct in that -type f -name '* *' only finds files with blanks in the name, but find prints the entire path including parent directories, so if you have
dir with blank/file with blank.txt
and you do rename 's/ //' on that string, you get
dirwith blank/file with blank.txt
because the first blank in the entire string was removed. And now the path has changed, invalidating previously found results.
You could
use a different incantation of
renameto a) only apply to the part after the last/and b) replace multiple blanks:find . -type f -name '* *' -print0 | xargs -0 rename -n 's| (?=[^/]*$)||g's/ (?=[^\/]*$)//gmatches all blanks that are followed by characters other than/until the end of the string, where(?=...)is a look-ahead.1 You can userename -nto dry-run until everything looks right.(with GNU
find) use-execdirto operate relative to the directory where the file is found, and also use Bash parameter expansion instead ofrename:find \ -type f \ -name '* *' \ -execdir bash -c 'for f; do mv "$f" "${f//[[:blank:]]}"; done' _ {}This collects as many matches as possible and then calls the Bash command with all the matches;
for fiterates over all positional parameters (i.e., each file), and themvcommand removes all blanks._is a stand-in for$0withinbash -cand doesn't really do anything.${f//[[:blank:]]}is a parameter expansion that removes all instances of[[:blank:]]from the string$f.You can use
echo mvuntil everything looks right.
1 There's an easier method to achieve the same using rename -d, see Renaud's answer.
