38

Does anyone have a tool or script that will recursively correct the file permissions on a directory?

On an Ubuntu Linux machine, a bunch of files were copied to a USB disk with full 777 permissions (user, group, other - read, write, execute) in error. I want to put them back in the user's directory corrected.

Directories should be 775 and all other files can be 664. All the files are images, documents or MP3s, so none of them need to be executable. If the directory bit is set then it needs execution, other wise it just needs user and group, read and write.

I figured it was worth checking if such a utility exists before hacking together a shell script :)

1

9 Answers 9

78

This should do the trick:

find /home/user -type d -print0 | xargs -0 chmod 0775
find /home/user -type f -print0 | xargs -0 chmod 0664
2
  • 3
    +1 for using -print0/xargs -0 :-).
    – sleske
    Dec 1, 2009 at 11:38
  • How can I exclude files by extension (e.g. .sh .py)
    – X3MBoy
    Mar 24, 2021 at 19:36
22

find can do the trick alone with -exec:

find /home/user -type f -exec chmod 0664 {} \;
find /home/user -type d -exec chmod 0775 {} \;

to prevent find from spawning a chmod for each entry:

find /home/user -type f -exec chmod 0664 {} +
find /home/user -type d -exec chmod 0775 {} +

(this effectively calls chmod once with the list of all files as parameters rather than one chmod per file)

3
  • 7
    Slower than using xargs, as you're forking a chmod for every file, where as xargs will run a chmod process with as many files as it can fit on a command line. On a very large tree, this can make a fair difference. Jul 2, 2009 at 15:43
  • 1
    Also, print0/xargs -0 will handle even very weird filenames; not so sure about find -exec.
    – sleske
    Dec 1, 2009 at 11:38
  • find exec can handle odd filenames. because argv[]
    – Jasen
    Aug 23, 2023 at 1:19
8

This answer won't solve your problem, but someone might find it useful for a similar problem where files have less permission than they should do.

# chmod -R . u=rwX,g=rX,o=rX

The magic is the X permission, rather than x. The chmod manpage describes it thus:

execute/search only if the file is a directory or already has execute permission for some user

This isn't suitable in your case as your files have execute permission so, will match the second test.

1
  • 1
    g should be rwX for 775 and 664.
    – aventurin
    Nov 12, 2016 at 15:51
2

In case that you're using ssh, it's good idea not to modify ~/.ssh permissions.

DIR=/home/user
find $DIR -type d -not -path "$DIR/.ssh" -print0 | xargs -0 chmod 0775
find $DIR -type f -not -path "$DIR/.ssh/*" -print0 | xargs -0 chmod 0664
2

I made a really simple bash script the other day because I needed to fix permissions. Why isn't there a formal utility to reset basic, non-root, file and folder permissions?

The script uses find to 755 all folders and 644 libraries. It then tests each file with readelf to see if it has a binary elf header. If not, it scans in the first two characters for shebang #!. It 755 those instances and 644 everything else after it checks to see if the file already has the matching permission.

Special cases are handled with an exception like the *.bak for files to be ignored.

#!/bin/bash
read -r -p "Correct file and folder permissions? [y/N] " chse
if [[ "$chse" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
  echo "Processing ..."
  find -H $(pwd) -type d -exec chmod 0755 {} \;
  # set dirs to 755
  find -H $(pwd) -type f \( -iname '*.so.*' -o -iname '*.so' \) -exec chmod 0644 {} \;
  # libs
  IFS=$'\n'
  for value in $(find -H $(pwd) -type f ! \( -iname '*.so.*' -o -iname '*.so' -o -iname '*.bak' \) -printf '%p\n'); do
    tstbin=$(readelf -l "$value" 2>/dev/null | grep -Pio 'executable|shared')
    if [ -z "$tstbin" ]; then
      tstbat=$(cat "$value" | head -c2 | grep -io '#!')
      if [ -n "$tstbat" ]; then
        perm=$(stat -c '%a' "$value")
        if [ "$perm" != "755" ]; then
          chmod 755 $value
          echo "Set script  755 $value"
          # set batch to 755
        fi
      else
        perm=$(stat -c '%a' "$value")
        if [ "$perm" != "644" ]; then
          chmod 644 $value
          echo "Set regular 644 $value"
          # set regular files to 644
        fi
      fi
      # above aren't elf binary
    else
      perm=$(stat -c '%a' "$value")
      if [ "$perm" != "755" ]; then
        chmod 755 $value
        echo "Set binary  755 $value"
        # set elf binaries to 755
      fi
    fi
  done
  unset IFS
  # process linux permissions for files and folders
else
  echo "Aborted."
fi
1
  • I have a different idea of what "simple" mean.
    – undg
    Aug 10, 2020 at 9:32
1

I made a script out of freiheit's solution, it adds basic argument checking.

#!/bin/sh

if [ $# -lt 1 ]; then
    echo "USAGE: $0 <path>"
    exit 1
fi

find $1 -type d -print0 | xargs -0 chmod 0755
find $1 -type f -print0 | xargs -0 chmod 0644
2
  • Good idea. However, the second line throws an error when the target is a directory: chmod: missing operand after ‘0644’. Can I suggest wrapping the find statements with a check to see if the target exists? if [ -d $1 ]; then find $1 -type d -print0 | xargs -0 chmod 0755; fi; if [ -f $1 ]; then find $1 -type f -print0 | xargs -0 chmod 0644; fi
    – Underverse
    Jun 4, 2017 at 6:22
  • It's almost correct; but if the target is a directory, you forgot to chmod the files in it, and if it's a file, you just need to chmod that file only.
    – sean
    Oct 6, 2019 at 14:53
0

An alternative, which does not exactly answer the original request, but is more likely what the OP intends, is to specify the permissions symbolically. E.g., I assume that OP wants to "remove write access for the public, and remove executable permission from all files".

This is possible if you use the symbolic permission representation.

  • o-w is "remove write access from the public" ("o" - others, "-" - remove, "w" - write)
  • a-x is "remove execute for everyone on files only" ("a" - all, "-" - remove, "x" - execute)

Writing it out this way instead of explicitly setting "0664" you avoid accidentally allowing extra permissions to files that were previously locked down. E.g., if a file was 0770 it will not become 0664 by mistake.

To do the first requirement you can just use chmod:

chmod --recursive o-w $dir

To do the second requirement:

find $dir -type f -exec chmod a-x {} +

Or to do both:

find $dir -type f -exec chmod a-x,o-w {} +
find $dir -type d -exec chmod o-w {} +
0

I use this script https://github.com/TheKito/Scripts/blob/master/FixHomesDirectoryPermissions.sh

#!/bin/bash
HME=/home
for i in $(ls "$HME");
do
        if [ -d "$HME/$i" ]; then
                echo "$i";
                chown -R $i "$HME/$i"
                chgrp -R $i "$HME/$i"
                find "$HME/$i" -type d -exec chmod 0770 "{}" \;
                find "$HME/$i" -type f -exec chmod 0660 "{}" \;
        fi
done;
-1

I found a simplified C-shell solution. This might not be completely fool proof but should work for most directories. It assumes the Unix 'file' command works, and finds exec files to reset them back to exec permissions:

find /home/user -type d | xargs chmod 0775
find /home/user -type f | xargs -0 chmod 0664
find /home/user -type f -exec file {} \; | grep executable | cut -d":" -f1 | xargs chmod 0775
1
  • 1
    This doesn't just repeat some of the answers, it introduces an error and functionality the OP didn't ask for.
    – RalfFriedl
    Mar 21, 2019 at 20:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .