在Python中使用sudo权限向文件写入

19
以下代码在非root用户运行且针对一个由root所有的文件时会抛出一个错误,即使非root用户拥有sudo权限也是如此。

以下代码在非root用户运行且针对一个由root所有的文件时会抛出一个错误,即使非root用户拥有sudo权限也是如此:

try:
  f = open(filename, "w+")
except IOError:
  sys.stderr.write('Error: Failed to open file %s' % (filename))
f.write(response + "\n" + new_line)
f.close()

有没有一种方法可以使用sudo权限运行open(filename, "w+"),或者有一个替代函数可以实现这个功能?


2
sudo python myscript.py - Robᵩ
你的意思是按照Rob的写法运行它吗?如果是这样,你能打印出真正的异常信息吗?except IOError as e: sys.stderr.write('Error: Failed to open file %s: %s' % (filename,str(e))) - Jean-François Fabre
7个回答

22

你有几个选项:

  • 以root身份或使用sudo运行您的脚本
  • 设置setuid位并使其属于root所有(尽管在许多系统上,此方法对于脚本不起作用,那么脚本将由任何人调用)
  • 检测自己没有以root身份运行(os.geteuid() != 0),然后在前面调用sudo(这将要求用户输入密码)并退出:

import os
import sys
import subprocess

if os.geteuid() == 0:
    print("We're root!")
else:
    print("We're not root.")
    subprocess.call(['sudo', 'python3', *sys.argv])
    sys.exit()

调用它看起来像这样:

$ python3 be_root.py
We're not root.
Password:
We're root!

我在运行您的示例时遇到了语法错误。我很确定星号参数展开仅适用于函数调用,而此处尝试在列表中使用它。您可以改为使用列表连接。请参见我的答案以获取更多细节。 - Peter Henry
@PeterHenry 你正在使用过时的Python版本,这是PEP 448 - L3viathan
2
我的错;我目前正在处理 Python 2.7 项目并且混淆了虚拟环境。我以为我在使用 Python 3。我会修正我的答案,使其成为 Python 2.7 兼容的解决方案。 - Peter Henry

10

简述:

L3viathan的回答 存在语法错误 编辑:在Python 3.5+上可以正常工作。这是适用于Python 3.4.3(默认分发于Ubuntu 16.04及以下版本)的版本:

if os.geteuid() == 0:
    # do root things
else:
    subprocess.call(['sudo', 'python3'] + sys.argv)  # modified

但是,在我的使用案例中,我选择了不同的方法,因为它使得周围的代码更简单:

if os.geteuid() != 0:
    os.execvp('sudo', ['sudo', 'python3'] + sys.argv)
# do root things

说明

L3viathan的回答依赖于PEP 448,该规范被包括在Python 3.5中,并引入了额外的上下文,使得星号参数展开在更多情况下被允许。对于Python 3.4及以下版本,可以使用列表连接来达到相同的效果:

import os
import sys
import subprocess

if os.geteuid() == 0:
    print("We're root!")
else:
    print("We're not root.")
    subprocess.call(['sudo', 'python3'] + sys.argv)  # modified
但请注意:subprocess.call()会启动一个子进程,这意味着在root完成运行脚本后,原始用户将继续运行他们的脚本。这意味着您需要将升级逻辑放在if/else块的一侧,以便在原始脚本完成时,它不会尝试运行任何需要提权的逻辑(L3viathan的示例就是如此)。
这并不一定是坏事 - 它意味着普通/升级的逻辑可以写在同一个脚本中并且可以很好地分离 - 但我的任务要求所有逻辑都需要以root权限运行。如果另一个块将为空,我不想浪费缩进级别,而且我没有意识到使用子进程会影响事情,所以我尝试了这个:
import os
import sys
import subprocess

if os.geteuid() != 0:
    subprocess.call(['sudo', 'python3'] + sys.argv)

# do things that require root

当然,它(指程序)崩溃了,因为在 root 完成后,常规用户恢复执行其脚本并尝试运行需要 root 权限的语句。

后来我发现了这个Gist,它推荐使用os.execvp() - 它会替换正在运行的进程而不是启动一个子进程:

import os
import sys

if os.geteuid() != 0:
    os.execvp('sudo', ['sudo', 'python3'] + sys.argv)  # final version

# do things that require root

这似乎表现如预期,节省了一个缩进级别和3行代码。

注意:十分钟前我还不知道os.execvp(),并且还不了解其使用可能存在的陷阱或细微差别。你的情况可能会有所不同。


1
我的代码没有语法错误,它只需要Python 3.5 - L3viathan
1
@L3viathan更新了我的答案以反映这一点。 干杯! - Peter Henry

5

另一种选择是将文件写入您有访问权限的目录,然后使用 sudo mv 命令将文件移动到您无权访问的目录。


2

您的脚本受到其运行权限的限制,如果没有超级用户权限,则无法更改用户。

正如Rob所说,如果不更改文件权限,唯一的方法是使用sudo运行。

sudo python ./your_file.py

2

如果你没有实际使用sudo,那么拥有使用sudo的可能性并不会给你任何特权。所以像其他人建议的那样,你可能应该直接使用sudo来启动你的程序。但是如果你不喜欢这个想法(我没有看到任何理由),你可以尝试其他方法。

你的脚本可以检查是否以root权限运行或仅以用户权限运行。然后脚本可以以更高的权限实际运行自己。下面是一个示例(请注意,将密码存储在源代码中不是一个好主意)。

import os.path
import subprocess

password_for_sudo = 'pass'

def started_as_root():
    if subprocess.check_output('whoami').strip() == 'root':
        return True
    return False

def runing_with_root_privileges():
    print 'I have the power!'

def main():
    if started_as_root():
        print 'OK, I have root privileges. Calling the function...'
        runing_with_root_privileges()
    else:
        print "I'm just a user. Need to start new process with root privileges..."
        current_script = os.path.realpath(__file__)
        os.system('echo %s|sudo -S python %s' % (password_for_sudo, current_script))

if __name__ == '__main__':
    main()

输出:

$ python test.py
I'm just a user. Need to start new process with root privileges...
OK, I have root privileges. Calling the function...
I have the power!

$ sudo python test.py
OK, I have root privileges.
Calling the function...
I have the power!

在我的Python3.5中,它是subprocess.check_output('whoami').strip() == b'root' - showkey

1

我喜欢L3viathan的回答。我想再加一个选项:使用subprocess.Popen()执行sh命令来写入文件。类似这样:subprocess.Popen('sudo echo \'{}\' > {}'.format(new_line, filename))


0

调用子进程的推荐方法是使用run()对于它可以处理的所有用例。这是这样一个情况。

#!/usr/bin/env python3

from os import geteuid
from subprocess import run
from sys import argv, exit


def main():
  if geteuid() == 0:
    print("We're root!")
    with open("file_owned_by_root", "w+") as f:
      conf_file="""{
"alt-speed-down": 50,
"alt-speed-enabled": false,
...
"upload-slots-per-torrent": 14,
"utp-enabled": true
}"""
      f.write(conf_file)
  else:
    print("We're not root.")
    run(["doas", *argv])
    exit()

if __name__ == "__main__":
  main()

#https://dev59.com/4FkS5IYBdhLWcg3wvI20


请注意,当您的脚本中有一个 shebang 时,就不需要将 'python3' 传递到 run() 的参数列表中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接