52

What's the "python way" to recursively set the owner and group to files in a directory? I could just pass a 'chown -R' command to shell, but I feel like I'm missing something obvious.

I'm mucking about with this:


import os  
path = "/tmp/foo"  
for root, dirs, files in os.walk(path):  
  for momo in dirs:  
    os.chown(momo, 502, 20)

This seems to work for setting the directory, but fails when applied to files. I suspect the files are not getting the whole path, so chown fails since it can't find the files. The error is:

'OSError: [Errno 2] No such file or directory: 'foo.html'

What am I overlooking here?

Julien Chastang
  • 17,592
  • 12
  • 63
  • 89
Geoff
  • 573
  • 1
  • 5
  • 7

10 Answers10

46

The dirs and files lists are all always relative to root - i.e., they are the basename() of the files/folders, i.e. they don't have a / in them (or \ on windows). You need to join the dirs/files to root to get their whole path if you want your code to work to infinite levels of recursion:


import os  
path = "/tmp/foo"
    
# Change permissions for the top-level folder
os.chmod(path, 502, 20)

for root, dirs, files in os.walk(path):
  # set perms on sub-directories  
  for momo in dirs:
    os.chown(os.path.join(root, momo), 502, 20)

  # set perms on files
  for momo in files:
    os.chown(os.path.join(root, momo), 502, 20)

Surprisingly, the shutil module doesn't have a function for this.

Avindra Goolcharan
  • 4,032
  • 3
  • 41
  • 39
too much php
  • 88,666
  • 34
  • 128
  • 138
  • 7
    This has a bug which I just saw in my co-worker's code in production :-) The top level directory specified is not being chowned. I suggested an edit with a fix, hopefully it gets approved. – Avindra Goolcharan Mar 21 '16 at 14:30
  • 5
    So my edit was rejected - good look to anyone who uses this and encounters the bug where `/tmp/foo` does NOT have its permissions changed. Good job moderating SO pythonians – Avindra Goolcharan Mar 21 '16 at 16:53
  • 4
    @AvindraGoolcharan good catch - hopefully that was what you had in mind! – fatal_error Apr 01 '16 at 02:03
  • 6
    No need to loop through `dirs` since `os.walk` will go through all directories anyway. See my [answer](https://stackoverflow.com/a/57458550/100217). – Christian Alis Aug 12 '19 at 09:27
  • 3
    @AvindraGoolcharan Edits [1](https://stackoverflow.com/review/suggested-edits/13116851), [2](https://stackoverflow.com/review/suggested-edits/11851085), strike [3](https://stackoverflow.com/review/suggested-edits/11715569) you're out. Apparently 9 reviewers think adding `os.chown(path, 502, 20)` "deviates from intent", "should be a comment", or "is worth making a whole new answer for". Really...? One line? Do all these people not know that `chmod -R some_dir` would change `some_dir` and all its child directories? I know you all are terrified about approving code edits, but still, this is silly – jrh Feb 08 '22 at 17:49
  • 1
    @AvindraGoolcharan, good catch! It's a shame your edit was rejected. Now even Copilot will suggest this snippet ... – Leo Apr 15 '23 at 19:09
  • @Leo thanks for the heads up. I added the fix and the comment. FWIW, GPT-4 suggests the original answer. After suggesting "Does this also change the permissions on the top-level (root) folder?" ChatGPT makes the correct edit to change the perms on the root folder. – Avindra Goolcharan May 03 '23 at 19:58
28

As correctly pointed out above, the accepted answer misses top-level files and directories. The other answers use os.walk then loop through dirnames and filenames. However, os.walk goes through dirnames anyway, so you can skip looping through dirnames and just chown the current directory (dirpath):

def recursive_chown(path, owner):
    for dirpath, dirnames, filenames in os.walk(path):
        shutil.chown(dirpath, owner)
        for filename in filenames:
            shutil.chown(os.path.join(dirpath, filename), owner)
Christian Alis
  • 6,556
  • 5
  • 31
  • 29
13

I could just pass a 'chown -R' command to shell

This is the simplest way, and gets lost in the question a bit, so just for clarity, you can do this in one line if you don't care about Windows:

os.system('chown -R 502 /tmp/foo')
crizCraig
  • 8,487
  • 6
  • 54
  • 53
  • 2
    This doesn't get enough credit. It would be nice for shutil to gain the recursive functionality, but until then, why bother.. – TTimo May 09 '23 at 01:10
7
import os  
path = "/tmp/foo"  
for root, dirs, files in os.walk(path):  
  for momo in dirs:  
    os.chown(momo, 502, 20)
  for file in files:
     fname = os.path.join(root, file)
     os.chown(fname, aaa, bb)

substitute aaa and bb as you please

Escualo
  • 40,844
  • 23
  • 87
  • 135
4

try os.path.join(root,momo) that will give you full path

Kugel
  • 19,354
  • 16
  • 71
  • 103
2

The accepted answer misses top level files. This is the actual equivalent of chown -R.

import os

path = "/tmp/foo"

os.chown(path, 502, 20)
for dirpath, dirnames, filenames in os.walk(path):
    for dname in dirnames:
        os.chown(os.path.join(dirpath, dname), 502, 20)
    for fname in filenames:
        os.chown(os.path.join(dirpath, fname), 502, 20)
nlsun
  • 381
  • 3
  • 9
  • 2
    No need to loop through `dirnames` since `os.walk` will go through all directories anyway. See my [answer](https://stackoverflow.com/a/57458550/100217). – Christian Alis Aug 12 '19 at 09:26
2

Here is a function i wrote that uses glob to recursively list files and change their permissions.

import os
import glob
def recursive_file_permissions(path,mode,uid=-1,gid=-1):
        '''
        Recursively updates file permissions on a given path.
        UID and GID default to -1, and mode is required
        '''
    for item in glob.glob(path+'/*'):
        if os.path.isdir(item):
            recursive_file_permissions(os.path.join(path,item),mode,uid,gid)
        else:
            try:
                os.chown(os.path.join(path,item),uid,gid)
                os.chmod(os.path.join(path,item),mode)
            except:
                print('File permissions on {0} not updated due to error.'.format(os.path.join(path,item)))

it's not perfect, but got me where I needed to be

1

Don't forget the for f in files loop, either. Similarly, remember to os.path.join(root, f) to get the full path.

dash-tom-bang
  • 17,383
  • 5
  • 46
  • 62
0
"""
Requires python 3
Accepts name or id
Usage:
  chown.py -p /temp/folder -u user  -g group -r true
  or
  chown.py -p /temp/folder -u uid -g gid -r 1
  user, group, and recursive are optional
  But must supply at least one of user or group
Example: sudo chown.py -p /temp/filename -u some_user 
"""
import argparse, os, sys
from shutil import chown
user = group = recursive = ''
parser=argparse.ArgumentParser()
parser.add_argument('-p', '--path')  # help='file/path'
parser.add_argument('-u', '--user')   # , help='user'
parser.add_argument( '-g','--group')   # , help='group'
parser.add_argument('-r', '--recursive', help=1)  # , help='recursive'

args=parser.parse_args()
path = args.path
if not path:
    raise Exception('missing path')
if args.user:
    user = args.user
if args.group:
    user = args.group
if args.recursive:
    recursive = True

if not user and not group:
    raise Exception('must supply user, group, or both')

def change_owner(path, user='', group='')
    if user and not group:
        chown(path, user=user)
    elif not user and group:
        chown(path, group=group)
    else:
        chown(path, user, group)

change_owner(path, user, group)
if recursive:
    for dirpath, dirnames, filenames in os.walk(path):
        for dname in dirnames:
            change_owner(os.path.join(dirpath, dname), user, group)
        for fname in filenames:
            change_owner(os.path.join(dirpath, fname), user, group)
stuartz
  • 121
  • 4
  • 9
-2

use os.lchown instead of os.chown for changing link themselves and files together.

foudfou
  • 976
  • 6
  • 11