142

Recently I am using Python module os, when I tried to change the permission of a file, I did not get the expected result. For example, I intended to change the permission to rw-rw-r--,

os.chmod("/tmp/test_file", 664)

The ownership permission is actually -w--wx--- (230)

--w--wx--- 1 ag ag 0 Mar 25 05:45 test_file

However, if I change 664 to 0664 in the code, the result is just what I need, e.g.

os.chmod("/tmp/test_file", 0664)

The result is:

-rw-rw-r-- 1 ag ag 0 Mar 25 05:55 test_file

Could anybody help explaining why does that leading 0 is so important to get the correct result?

AplusG
  • 1,469
  • 2
  • 10
  • 9

7 Answers7

170

Found this on a different forum

If you're wondering why that leading zero is important, it's because permissions are set as an octal integer, and Python automagically treats any integer with a leading zero as octal. So os.chmod("file", 484) (in decimal) would give the same result.

What you are doing is passing 664 which in octal is 1230

In your case you would need

os.chmod("/tmp/test_file", 436)

[Update] Note, for Python 3 you have prefix with 0o (zero oh). E.G, 0o666

Mawg says reinstate Monica
  • 38,334
  • 103
  • 306
  • 551
RedBaron
  • 4,717
  • 5
  • 41
  • 65
152

So for people who want semantics similar to:

$ chmod 755 somefile

Use:

$ python -c "import os; os.chmod('somefile', 0o755)"

If your Python is older than 2.6:

$ python -c "import os; os.chmod('somefile', 0755)"
Nick T
  • 25,754
  • 12
  • 83
  • 121
Dima
  • 2,012
  • 2
  • 17
  • 23
  • 12
    The python3 format works in python 2.7.9 as well. I have not checked earlier versions. – Fred Mitchell Mar 03 '16 at 13:11
  • 3
    The Python 3 syntax works back to Python 2.6 https://docs.python.org/3/whatsnew/2.6.html#pep-3127-integer-literal-support-and-syntax – Pete Apr 08 '18 at 14:51
  • Work for me tks! – LandiLeite Dec 12 '18 at 00:50
  • 1
    Should probably be `00755`, just to make it clear where the suid/sgid/sticky bits go, in the event that some later developer comes through and wants to make this old script use, for example, sgid with `2755` but then can't figure out why the perms are completely screwed up. ;) – dannysauer Feb 04 '20 at 22:32
23

Use permission symbols (stat.S_I*) instead of raw octal numbers

Your problem would have been avoided if you had used the more semantically named permission symbols rather than raw magic numbers, e.g. for 664:

#!/usr/bin/env python3

import os
import stat

os.chmod(
    'myfile',
    stat.S_IRUSR |
    stat.S_IWUSR |
    stat.S_IRGRP |
    stat.S_IWGRP |
    stat.S_IROTH
)

This is documented at https://docs.python.org/3/library/os.html#os.chmod and the names are the same as the POSIX C API which is also present at man 2 stat and man 2 chmod:

S_IRUSR  (00400)  read by owner
S_IWUSR  (00200)  write by owner
S_IXUSR  (00100)  execute/search by owner
S_IRGRP  (00040)  read by group
S_IWGRP  (00020)  write by group
S_IXGRP  (00010)  execute/search by group
S_IROTH  (00004)  read by others
S_IWOTH  (00002)  write by others
S_IXOTH  (00001)  execute/search by others

Another advantage is the greater portability as mentioned in the docs:

Note: Although Windows supports chmod(), you can only set the file’s read-only flag with it (via the stat.S_IWRITE and stat.S_IREAD constants or a corresponding integer value). All other bits are ignored.

chmod +x is demonstrated at: How do you do a simple "chmod +x" from within python?

Tested in Ubuntu 16.04, Python 3.5.2.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
13

leading 0 means this is octal constant, not the decimal one. and you need an octal to change file mode.

permissions are a bit mask, for example, rwxrwx--- is 111111000 in binary, and it's very easy to group bits by 3 to convert to the octal, than calculate the decimal representation.

0644 (octal) is 0.110.100.100 in binary (i've added dots for readability), or, as you may calculate, 420 in decimal.

lenik
  • 23,228
  • 4
  • 34
  • 43
13

If you have desired permissions saved to string then do

s = '660'
os.chmod(file_path, int(s, base=8))
mc.dev
  • 2,675
  • 3
  • 21
  • 27
5

@mc.dev's answer was the best answer here I ended up leveraging that to make the below function wrapper for reuse. Thanks for the share.

def chmod_digit(file_path, perms):
    """
    Helper function to chmod like you would in unix without having to preface 0o or converting to octal yourself.
    Credits: https://stackoverflow.com/a/60052847/1621381
    """
    os.chmod(file_path, int(str(perms), base=8))
Mike R
  • 679
  • 7
  • 13
0

Using the stat.* bit masks does seem to me the most portable and explicit way of doing this. But on the other hand, I often forget how best to handle that. So, here's an example of masking out the 'group' and 'other' permissions and leaving 'owner' permissions untouched. Using bitmasks and subtraction is a useful pattern.

import os
import stat
def chmodme(pn):
    """Removes 'group' and 'other' perms. Doesn't touch 'owner' perms."""
    mode = os.stat(pn).st_mode
    mode -= (mode & (stat.S_IRWXG | stat.S_IRWXO))
    os.chmod(pn, mode)
Jason Drew
  • 281
  • 2
  • 8