Home > Enterprise >  Removing white spaces from files but not from directories throws an error
Removing white spaces from files but not from directories throws an error

Time:10-28

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 use rename -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 of rename:

    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 the mv command removes all blanks. _ is a stand-in for $0 within bash -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.

  • Related