Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using find results when directories have spaces in their names

Tags:

bash

shell

I'm trying to change the permissions of all the subdirectories by making a simple bash for loop:

for dir in `find . -type d`; do chmod 755 "$dir"; done

however, it complains about non-existing directories. By simply printing the directory names from the loop (replacing chmod 755 "$dir" with echo "$dir") I've worked out that the problem occurs when a directory has spaces in its name. What happens is that the for loop splits the results of find on every newline and space.

I'd like to somehow make it split the results only according to newlines and ignore the spaces. The double quotes should make sure that the string reaches chmod as one argument. How do I change the splitting?

like image 363
Giedrius Kudelis Avatar asked Feb 22 '13 17:02

Giedrius Kudelis


3 Answers

The usual solution to this problem is:

find ... -print0 | xargs -0 ...

The -print0 argument causes the output filenames to be nul-terminated and the -0 / -null argument to xargs tells it to read such a format.

So in your case...

$ find . -type d -a -print0 | xargs -0 chmod 755

You don't actually need the shell loop at all.

like image 153
DigitalRoss Avatar answered Oct 18 '22 01:10

DigitalRoss


Here are some more options for your toolkit. If you just have a single command to execute on groups of files/directories, find can do it directly:

find . -type d -exec chmod 755 {} +

If the command can only work on one file/directory at a time, use \; instead of + to run once for each item:

find . -type d -exec chmod 755 {} \;

And if you need to do something complex (i.e. several commands), you can make a safe version of the loop:

while IFS= read -r -u3 -d $'\0' dir; do
    sudo chown gkudelis "$dir"
    chmod 755 "$dir"
    touch "$dir/.perms_reset"
done 3< <(find /tmp -type f -print0)

Note that this uses a redirect from a process substitution (<(...)) instead of a pipe to avoid running the loop in a subshell; this is a bash-only feature, so you must start the script with #!/bin/bash, not #!/bin/sh. Also, it does the redirect to fd 3 instead of stdin, so if anything inside the loop tries to read from stdin it won't get confused. Also, be sure to use double-quotes around all references to the loop variable ("$dir" in this example) to protect spaces in it. See BashFAQ #020 for more details.

like image 4
Gordon Davisson Avatar answered Oct 18 '22 02:10

Gordon Davisson


Try doing this instead (using builtins, requires bash --version >= 4) :

shopt -s globstar
for dir in **/*/; do
    chmod 0755 "${dir%/}"
done
like image 1
Gilles Quenot Avatar answered Oct 18 '22 03:10

Gilles Quenot