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
rename
to 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/ (?=[^\/]*$)//g
matches all blanks that are followed by characters other than/
until the end of the string, where(?=...)
is a look-ahead.1 You can userename -n
to dry-run until everything looks right.(with GNU
find
) use-execdir
to 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 f
iterates over all positional parameters (i.e., each file), and themv
command removes all blanks._
is a stand-in for$0
withinbash -c
and 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 mv
until everything looks right.
1 There's an easier method to achieve the same using rename -d
, see Renaud's answer.