762

How to chmod 755 all directories but not files (recursively)?

Inversely, how to chmod only files (recursively) but no directories?

Wouter
  • 139
  • 1
  • 8
Olivier Lalonde
  • 10,107
  • 7
  • 29
  • 33
  • Related: [Change all files and folders permissions of a directory to 644/755](http://stackoverflow.com/q/18817744/55075) at SO – kenorb Aug 10 '15 at 12:16
  • Related: [Change all folder permissions with one command](https://unix.stackexchange.com/q/349053/23408) at U & L. – Scott - Слава Україні Oct 09 '18 at 23:07
  • If you're just wanting to make directories executable without making files executable, recursively, you can set your file permissions on everything, including directories first (e.g. `sudo chmod -R 0644 myDirectory`), and then do `sudo chmod -R +X myDirectory`. – Brōtsyorfuzthrāx Oct 25 '22 at 07:05

10 Answers10

1068

To recursively give directories read&execute privileges:

find /path/to/base/dir -type d -exec chmod 755 {} +

To recursively give files read privileges:

find /path/to/base/dir -type f -exec chmod 644 {} +

Or, if there are many objects to process:

chmod 755 $(find /path/to/base/dir -type d)
chmod 644 $(find /path/to/base/dir -type f)

Or, to reduce chmod spawning:

find /path/to/base/dir -type d -print0 | xargs -0 chmod 755 
find /path/to/base/dir -type f -print0 | xargs -0 chmod 644
Bob Stein
  • 1,383
  • 1
  • 16
  • 23
nik
  • 55,788
  • 10
  • 98
  • 140
  • 20
    The first two examples fail for directories with too many files: `-bash: /bin/chmod: Argument list too long`. The last command works with many files, but when using `sudo` one must be careful to put it before xargs instead of chmod: `find /path/to/base/dir -type d -print0 | sudo xargs -0 chmod 755 ` – Agargara Nov 07 '17 at 01:06
  • 5
    Also to note, these commands are *inclusive* of the base dir. So in the above example, `dir` will also be set to 755. – CenterOrbit Jan 16 '18 at 00:16
  • 4
    `chmod ... $(find /path/to/base/dir -type ...)` fails for filenames with spaces in the name. – Dan Dascalescu Feb 06 '18 at 01:58
  • 9
    I think the most correct (but not fastest) version with respect to spaces and symbols in filenames and number of files is `find /path/to/base/dir -type d -exec chmod 755 {} \;` (`find /path/to/base/dir -type f -exec chmod 644 {} \;`). – Peter K Feb 21 '18 at 11:49
  • Note that this only goes to the first layer it can read. You will have to execute it several times to get deeper into the directory tree. – Henk Poley Apr 14 '19 at 18:56
  • `xargs` can also use the `-r` option so `chmod` doesn't run when no directories are found by `find`. (Useful for acting on subdirectories only.) – Adambean Jun 30 '20 at 07:51
  • 2
    what does the plus do after the find exec? [Turns out](https://stackoverflow.com/a/6085237/2550406) it makes `find` run `chmod` once with all arguments instead of multiple times with each filename separately – lucidbrot Apr 24 '22 at 11:18
  • 1
    With find, make sure to put single quotes around the filename: `'{}'`. Without them, it will break if the filename has a space, $, or similar characters. – SArcher May 27 '22 at 17:48
371

A common reason for this sort of thing is to set directories to 755 but files to 644. In this case there's a slightly quicker way than nik's find example:

chmod -R u+rwX,go+rX,go-w /path

Meaning:

  • -R = recursively;
  • u+rwX = Users can read, write and execute;
  • go+rX = group and others can read and execute;
  • go-w = group and others can't write

The important thing to note here is that uppercase X acts differently to lowercase x. In manual we can read:

The execute/search bits if the file is a directory or any of the execute/search bits are set in the original (unmodified) mode.

In other words, chmod u+X on a file won't set the execute bit; and g+X will only set it if it's already set for the user.

PetaspeedBeaver
  • 384
  • 2
  • 7
  • 15
bobince
  • 9,716
  • 2
  • 21
  • 18
  • 5
    -R = recursively; u+rwX = Users can read, write and execute; go+rX = group and others can read and execute; go-w = group and others can't write – släcker Jan 06 '10 at 07:08
  • 29
    This pattern won't fix the situation when someone has done `chmod -R 777` since the `+X` option will not reset existing execute bits on files. Using -x will reset directories, and prevent descending into them. – Andrew Vit Aug 07 '12 at 04:57
  • But how does your command answer the OP's question: set different ACL depending on file / dir? Your command will set ACL regardless if it's a dir or a file – Déjà vu Oct 27 '12 at 17:44
  • 4
    @ring0: I am not intending to answer the question literally as posed - nik has already done that perfectly well. I'm pointing out a cheaper solution for the most common case. And yes, you do get different permissions for files and directories with `X`, as explained in the comments. – bobince Oct 28 '12 at 01:05
  • 12
    `go+rX,go-w` -> `go=rX` isn't it ? – Pierre de LESPINAY Sep 22 '14 at 14:24
  • `chmod -R u+rwX,g+rwX,o+rX *` made my life so much easier when running fedora which has a fit on permissions for serving documents. – Tiny Giant May 04 '15 at 18:57
  • 10
    You can also use `chmod u-x,u+X` in combination, etc., to remove execute bits for files, but add them for directories. – w0rp Apr 04 '16 at 18:27
19

If you want to make sure the files are set to 644 and there are files in the path which have the execute flag, you will have to remove the execute flag first. +X doesn't remove the execute flag from files who already have it.

Example:

chmod -R ugo-x,u+rwX,go+rX,go-w path

Update: this appears to fail because the first change (ugo-x) makes the directory unexecutable, so all the files underneath it are not changed.

mpolden
  • 199
  • 3
  • 2
    This works for me, and I don’t see why it wouldn’t. (Sure, if you did just `chmod -R ugo-x path`, that might be a problem. But the complete command will do the `chmod u+rwX` on each directory before it tries to descend into it.) However, I believe that `chmod R u=rw,go=r,a+X path` is sufficient – and it’s shorter. – Scott - Слава Україні Jul 08 '14 at 00:25
6

To recursively give directories read&execute privileges:

find /path/to/base/dir -type d -exec chmod 755 {} \;

To recursively give files read privileges:

find /path/to/base/dir -type f -exec chmod 644 {} \;

Better late than never let me upgrade nik's answer on the side of correctness. My solution is slower, but it works with any number of files, with any symbols in filenames, and you can run it with sudo normally (but beware that it might discover different files with sudo).

Peter K
  • 359
  • 3
  • 5
4

I decided to write a little script for this myself.

Recursive chmod script for dirs and/or files — Gist:

chmodr.sh

#!/bin/sh
# 
# chmodr.sh
#
# author: Francis Byrne
# date: 2011/02/12
#
# Generic Script for recursively setting permissions for directories and files
# to defined or default permissions using chmod.
#
# Takes a path to recurse through and options for specifying directory and/or 
# file permissions.
# Outputs a list of affected directories and files.
# 
# If no options are specified, it recursively resets all directory and file
# permissions to the default for most OSs (dirs: 755, files: 644).

# Usage message
usage()
{
  echo "Usage: $0 PATH -d DIRPERMS -f FILEPERMS"
  echo "Arguments:"
  echo "PATH: path to the root directory you wish to modify permissions for"
  echo "Options:"
  echo " -d DIRPERMS, directory permissions"
  echo " -f FILEPERMS, file permissions"
  exit 1
}

# Check if user entered arguments
if [ $# -lt 1 ] ; then
 usage
fi

# Get options
while getopts d:f: opt
do
  case "$opt" in
    d) DIRPERMS="$OPTARG";;
    f) FILEPERMS="$OPTARG";;
    \?) usage;;
  esac
done

# Shift option index so that $1 now refers to the first argument
shift $(($OPTIND - 1))

# Default directory and file permissions, if not set on command line
if [ -z "$DIRPERMS" ] && [ -z "$FILEPERMS" ] ; then
  DIRPERMS=755
  FILEPERMS=644
fi

# Set the root path to be the argument entered by the user
ROOT=$1

# Check if the root path is a valid directory
if [ ! -d $ROOT ] ; then
 echo "$ROOT does not exist or isn't a directory!" ; exit 1
fi

# Recursively set directory/file permissions based on the permission variables
if [ -n "$DIRPERMS" ] ; then
  find $ROOT -type d -print0 | xargs -0 chmod -v $DIRPERMS
fi

if [ -n "$FILEPERMS" ] ; then
  find $ROOT -type f -print0 | xargs -0 chmod -v $FILEPERMS
fi

It basically does the recursive chmod but also provides a bit of flexibility for command line options (sets directory and/or file permissions, or exclude both it automatically resets everything to 755-644). It also checks for a few error scenarios.

I also wrote about it on my blog.

fixer1234
  • 27,064
  • 61
  • 75
  • 116
francisbyrne
  • 49
  • 1
  • 2
2

Try this python script; it requires no spawning of processes and does only two syscalls per file. Apart from an implementation in C, it will probably be the fastest way of doing it (I needed it to fix a filesystem of 15 million files which were all set to 777)

#!/usr/bin/python3
import os
for par, dirs, files in os.walk('.'):
    for d in dirs:
        os.chmod(par + '/' + d, 0o755)
    for f in files:
        os.chmod(par + '/' + f, 0o644)

In my case, a try/catch was required around the last chmod, since chmodding some special files failed.

mic_e
  • 299
  • 2
  • 5
1

I post my solution because I don't see an almost-every cases solution using only chmod:

Only chmod : smooth permissions on files and dirs

For my example I created multiple files with different permissions:

> tree -p chmodtests/
chmodtests/
├── [drwxr-xr-x]  aa/
│   ├── [drwxr-xr-x]  a1/
│   │   ├── [-r--r--r--]  read_only
│   │   ├── [-rw-rw-rw-]  read_w
│   │   └── [-rwxrwxrwx]  read_wx*
│   ├── [drwxr-xr-x]  a2/
│   ├── [-r--------]  read_only
│   ├── [-rw-------]  read_w
│   └── [-rwx------]  read_wx*
└── [drwxr-xr-x]  bb/

4 directories, 6 files

then apply this command:

chmod -vR a=r-wx,u=wr,a+X chmodtests/

output:

mode of 'chmodtests/' retained as 0755 (rwxr-xr-x)
mode of 'chmodtests/aa' retained as 0755 (rwxr-xr-x)
mode of 'chmodtests/aa/a1' retained as 0755 (rwxr-xr-x)
mode of 'chmodtests/aa/a1/read_only' changed from 0444 (r--r--r--) to 0644 (rw-r--r--)
mode of 'chmodtests/aa/a1/read_w' changed from 0666 (rw-rw-rw-) to 0644 (rw-r--r--)
mode of 'chmodtests/aa/a1/read_wx' changed from 0777 (rwxrwxrwx) to 0644 (rw-r--r--)
mode of 'chmodtests/aa/read_only' changed from 0400 (r--------) to 0644 (rw-r--r--)
mode of 'chmodtests/aa/a2' retained as 0755 (rwxr-xr-x)
mode of 'chmodtests/aa/read_w' changed from 0600 (rw-------) to 0644 (rw-r--r--)
mode of 'chmodtests/aa/read_wx' changed from 0700 (rwx------) to 0644 (rw-r--r--)
mode of 'chmodtests/bb' retained as 0755 (rwxr-xr-x)

result: all files are 644; all dirs are 755

> tree -p chmodtests/
chmodtests/
├── [drwxr-xr-x]  aa/
│   ├── [drwxr-xr-x]  a1/
│   │   ├── [-rw-r--r--]  read_only
│   │   ├── [-rw-r--r--]  read_w
│   │   └── [-rw-r--r--]  read_wx
│   ├── [drwxr-xr-x]  a2/
│   ├── [-rw-r--r--]  read_only
│   ├── [-rw-r--r--]  read_w
│   └── [-rw-r--r--]  read_wx
└── [drwxr-xr-x]  bb/

Explanation part

tl;dr explanation:

this command removes all execution/search on files and directories and then add execution/search only for dirs

chmod -vR : verbose and recursive

a=r-wx:

  • a: meaning all (user, group and other)
  • =: set permissions to (do not add nor remove)
  • r-wx: read only permissions

u=wr: user can read and write

a+X: add execution/search only for directories (for all types u,g,o)

Other example

Now let's say I only want 600 for files and 700 for dirs:

chmod -vR a=-rwx,u=rw,u+X chmodtests/

Limits

With this method you cannot set r and w differently for file and dirs.

E.g. you cannot have the following

drwxr-xr-x dir/
-r-------- dir/myfile

hth

Boop
  • 156
  • 8
  • My particulary choice on Samba share: (for hidden files include: shopt -s dotglob;) chmod -R a=-rwx,u=rw,g=rw,u+X,g+X * – user1855805 Mar 03 '21 at 14:47
0

Built on top of nik's answer for convenience while still being minimal:

#!/bin/bash

me=`basename "$0"`
if [[ -z $3 || $1 == "help" || $1 == "-h" || $1 == "--help" ]]; then
    echo "Usage: $me <directory_permissions> <file_permissions> <path> [ -s ]"
    exit 0
fi
terminator=+
if [[ $1 == "-s" ]]; then
    terminator=\;
fi
find $3 -type f -exec chmod $2 '{}' $terminator
find $3 -type d -exec chmod $1 '{}' $terminator

The -s will enable secure/safe mode which is required if you deal with a ton of files, because the chained command would exceed the argument limit of the individual command.

This implementation is not prone to spaces in paths.

Martin Braun
  • 681
  • 2
  • 10
  • 22
-1

You can also use tree:

tree -faid /your_directory | xargs -L1 -I{} bash -c 'sudo chmod 755 "$1"' -- '{}'

and if you want to also view the folder add an echo

 tree -faid /your_directory | xargs -L1 -I{} bash -c 'sudo chmod 755 "$1" && echo$1' -- '{}'
Eduard Florinescu
  • 2,946
  • 7
  • 34
  • 45
  • @Scott 1) You are right about +x I changed to 755; 2) 3) to solve this I put the placeholder in single quote like this '{}' – Eduard Florinescu Oct 10 '18 at 05:53
  • @Scott I agree this is not the best answer also is slow but will leave here for "didactic" purposes also the comments will explain further, also people can learn about `xargs` issues. Single quotes in filenames are themselves a problem for many commands and script that's why I listed all the files containing single quotes and removed them (the quotes I mean) – Eduard Florinescu Oct 10 '18 at 06:16
  • @Scott On my systems I searched for all the files that contained single quotes and replaced the single quotes – Eduard Florinescu Oct 10 '18 at 06:42
  • @Scott How would you fix the fact that xargs doesn't solve correctly the single quotes? – Eduard Florinescu Oct 10 '18 at 07:20
  • Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/84264/discussion-between-eduard-florinescu-and-scott). – Eduard Florinescu Oct 10 '18 at 07:26
-2

You could use the following bash script as an example. Be sure to give it executable permissions (755). Simply use ./autochmod.sh for the current directory, or ./autochmod.sh <dir> to specify a different one.

#!/bin/bash

if [ -e $1 ]; then
    if [ -d $1 ];then
        dir=$1
    else
        echo "No such directory: $1"
        exit
    fi
else
    dir="./"
fi

for f in $(ls -l $dir | awk '{print $8}'); do
    if [ -d $f ];then
        chmod 755 $f
    else
        chmod 644 $f
    fi
done
user26528
  • 280
  • 1
  • 2
  • 3
    Wow! So many problems! (1) If `$1` is not null, but is not the name of a directory (e.g., is a typo), then `dir` gets set to `.` with no message. (2) `$1` should be `"$1"` and `$dir` should be `"$dir"`. (3) You don’t need to say `"./"`; `"."` is fine (and, strictly speaking, you don’t need quotes here). (4) This is not a recursive solution. (5) On my system, `ls -l … | awk '{ print $8 }'` gets the files’ modification times. You need `{ print $9 }` to get _the first word of_ the filename. And even then, (6) this does not handle filenames with white space. … – Scott - Слава Україні Jul 07 '14 at 22:59
  • 2
    … And, last but not least (∞) if this script is in the current directory, it will `chmod` _itself_ to 644, thus making itself non-executable! – Scott - Слава Україні Jul 07 '14 at 23:00