172

I want to create a file from within a python script that is executable.

import os
import stat
os.chmod('somefile', stat.S_IEXEC)

it appears os.chmod doesn't 'add' permissions the way unix chmod does. With the last line commented out, the file has the filemode -rw-r--r--, with it not commented out, the file mode is ---x------. How can I just add the u+x flag while keeping the rest of the modes intact?

ArtOfWarfare
  • 20,617
  • 19
  • 137
  • 193
priestc
  • 33,060
  • 24
  • 83
  • 117

9 Answers9

267

Use os.stat() to get the current permissions, use | to OR the bits together, and use os.chmod() to set the updated permissions.

Example:

import os
import stat

st = os.stat('somefile')
os.chmod('somefile', st.st_mode | stat.S_IEXEC)
wjandrea
  • 28,235
  • 9
  • 60
  • 81
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 4
    This only makes it executable by the user. The poster was asking about "chmod +x" which makes it executable across the board (user, group, world) – eric.frederich Aug 13 '13 at 14:11
  • 41
    Use the following to make it executable by everyone... stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH. Note: that value is the same as octal 0111, so you could just do st.st_mode | 0111 – eric.frederich Aug 13 '13 at 14:18
  • 2
    [My answer below](http://stackoverflow.com/a/30463972/119527) copies the R bits to X, as one would expect from say, a compiler. – Jonathon Reinhart May 26 '15 at 16:25
  • 1
    I would do `STAT_OWNER_EXECUTABLE = stat.S_IEXEC`, and use the human readable local constant instead of the gibberish one. – ThorSummoner Dec 22 '15 at 21:31
  • 2
    here's a non-pythonic answer that may be a little more readable: `subprocess.check_call(['chmod', '+x', 'somefile'])` and let's you more easily do operations like `a+rx`. – Trevor Boyd Smith Aug 06 '20 at 15:28
  • Would it not be better to use XOR not OR as then you are only changing the Executable bits of the permission and it is reversible so you are left with the permissions which the file had before being made executable `os.chmod('test.sh', os.stat('test.sh').st_mode ^ 111)` – REA_ANDREW Oct 05 '21 at 09:50
  • Does this actually work on Windows? – Jean-Francois T. Feb 17 '23 at 08:04
32

For tools that generate executable files (e.g. scripts), the following code might be helpful:

def make_executable(path):
    mode = os.stat(path).st_mode
    mode |= (mode & 0o444) >> 2    # copy R bits to X
    os.chmod(path, mode)

This makes it (more or less) respect the umask that was in effect when the file was created: Executable is only set for those that can read.

Usage:

path = 'foo.sh'
with open(path, 'w') as f:           # umask in effect when file is created
    f.write('#!/bin/sh\n')
    f.write('echo "hello world"\n')

make_executable(path)
Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328
  • 2
    Octal literals changed in Python 3. Instead of `0444`, you'd use `0o444`. Or, if you want to support both, just write `292`. – Kevin Jun 07 '15 at 19:37
  • 1
    @Kevin It [looks like](https://docs.python.org/3.0/whatsnew/3.0.html#new-syntax) the new syntax has been supported by Python 2.6, so it seems reasonable to use that. (For a compatibility reference point, CentOS 6 ships with Python 2.6). – Jonathon Reinhart Jun 08 '15 at 00:33
  • 2
    I was unaware that Python 3 had remove the traditional octal literals. So thank you for that. – Jonathon Reinhart Jun 08 '15 at 01:37
  • 1
    @AaravPrasad No, it is not the same. You're turning on all X bits while my answer only turns on X bits where R is already on. Also, being a "one-liner" is of absolutely zero value for any real project. – Jonathon Reinhart Jan 06 '23 at 15:09
  • @AaravPrasad You don't seem to understand what [`umask`](https://manpages.org/umask/2) is or how it works. Even if a process passes `0o777` for the mode to `open()`, the kernel will *mask*-off bits that are set in the process's current `umask`. So if the `umask` is `0o027`, then the resulting file will have mode `0o750`. And as stated in my answer, "This makes it (more or less) respect the `umask` that was in effect when the file was created: Executable is only set for those that can read." When would you ever want a file to be executable by someone who cannot read it? It doesn't make sense. – Jonathon Reinhart Jan 19 '23 at 21:19
26

If you're using Python 3.4+, you can use the standard library's convenient pathlib.

Its Path class has built-in chmod and stat methods.

from pathlib import Path
import stat


f = Path("/path/to/file.txt")
f.chmod(f.stat().st_mode | stat.S_IEXEC)
cs01
  • 5,287
  • 1
  • 29
  • 28
18

If you know the permissions you want then the following example may be the way to keep it simple.

Python 2:

os.chmod("/somedir/somefile", 0775)

Python 3:

os.chmod("/somedir/somefile", 0o775)

Compatible with either (octal conversion):

os.chmod("/somedir/somefile", 509)

reference permissions examples

Drise
  • 4,310
  • 5
  • 41
  • 66
zerocog
  • 1,703
  • 21
  • 32
9

Respect umask like chmod +x

man chmod says that if augo is not given as in:

chmod +x mypath

then a is used but with umask:

A combination of the letters ugoa controls which users' access to the file will be changed: the user who owns it (u), other users in the file's group (g), other users not in the file's group (o), or all users (a). If none of these are given, the effect is as if (a) were given, but bits that are set in the umask are not affected.

The goal of this is so that you don't accidentally give too many permissions. umask determines the default permissions of a new file, e.g. with a umask of 0077, touch newfile.txt produces permissions rw for the current user because the 77 would exclude group and other (x is not given by default by touch anyways though). And chmod +x would similarly only add +x for user, ignoring group and other due to the 0011 part of the mask: you would need chmod o+x, chmod g+x, chmod go+x or chmod a+x to force them to be set.

Here is a version that simulates that behavior exactly:

#!/usr/bin/env python3

import os
import stat

def get_umask():
    umask = os.umask(0)
    os.umask(umask)
    return umask

def chmod_plus_x(path):
    os.chmod(
        path,
        os.stat(path).st_mode |
        (
            (
                stat.S_IXUSR |
                stat.S_IXGRP |
                stat.S_IXOTH
            )
            & ~get_umask()
        )
    )

chmod_plus_x('.gitignore')

See also: How can I get the default file permissions in Python?

Tested in Ubuntu 16.04, Python 3.5.2.

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

You can also do this

>>> import os
>>> st = os.stat("hello.txt")

Current listing of file

$ ls -l hello.txt
-rw-r--r--  1 morrison  staff  17 Jan 13  2014 hello.txt

Now do this.

>>> os.chmod("hello.txt", st.st_mode | 0o111)

and you will see this in the terminal.

ls -l hello.txt    
-rwxr-xr-x  1 morrison  staff  17 Jan 13  2014 hello.txt

You can bitwise or with 0o111 to make all executable, 0o222 to make all writable, and 0o444 to make all readable.

ncmathsadist
  • 4,684
  • 3
  • 29
  • 45
0

This turns on all executable bits for a file:

os.chmod("path", os.stat("path").st_mode | 0o111)

Anm
  • 447
  • 4
  • 15
-1

In python3:

import os
os.chmod("somefile", 0o664)

Remember to add the 0o prefix since permissions are set as an octal integer, and Python automatically treats any integer with a leading zero as octal. Otherwise, you are passing os.chmod("somefile", 1230) indeed, which is octal of 664.

funkid
  • 577
  • 1
  • 10
  • 30
-1

we can directly call chmod +x command in python using os.system()

import os
os.system("chmod +x somefile")