这里是文章模块栏目内容页
Python使用 subprocess 模块

本文通过subprocess模块获取nginx 的配置参数,代码如下:

 """
Nginx config parser and pattern builder.
"""
import os
import re
import subprocess

from pyparsing import Literal, Word, ZeroOrMore, OneOrMore, Group, \
    printables, quotedString, pythonStyleComment, removeQuotes

from .utils import choose_one, error_exit


REGEX_SPECIAL_CHARS = r'([\.\*\+\?\|\(\)\{\}\[\]])'
REGEX_LOG_FORMAT_VARIABLE = r'\$([a-zA-Z0-9\_]+)'
LOG_FORMAT_COMBINED = '$remote_addr - $remote_user [$time_local] ' \
                      '"$request" $status $body_bytes_sent ' \
                      '"$http_referer" "$http_user_agent"'
LOG_FORMAT_COMMON   = '$remote_addr - $remote_user [$time_local] ' \
                      '"$request" $status $body_bytes_sent ' \
                      '"$http_x_forwarded_for"'
                      
# common parser element
semicolon = Literal(';').suppress()
# nginx string parameter can contain any character except: { ; " '
parameter = Word(''.join(c for c in printables if c not in set('{;"\'')))
# which can also be quoted
parameter = parameter | quotedString.setParseAction(removeQuotes)


def detect_config_path():                      
    """
    Get nginx configuration file path based on `nginx -V` output
    :return: detected nginx configuration file path
    """
    try:
        proc = subprocess.Popen(['nginx', '-V'], stderr=subprocess.PIPE)
    except OSError:
        error_exit('Access log file or format was not set and nginx config file cannot be detected. ' +
                   'Perhaps nginx is not in your PATH?')

    stdout, stderr = proc.communicate()
    version_output = stderr.decode('utf-8')
    conf_path_match = re.search(r'--conf-path=(\S*)', version_output)
    if conf_path_match is not None:
        return conf_path_match.group(1)

    prefix_match = re.search(r'--prefix=(\S*)', version_output)
    if prefix_match is not None:
        return prefix_match.group(1) + '/conf/nginx.conf'
    return '/etc/nginx/nginx.conf'


推荐的调用子进程的方式是在任何它支持的用例中使用 run() 函数。对于更进阶的用例,也可以使用底层的 Popen接口。

run() 函数是在 Python 3.5 被添加的;如果你需要与旧版本保持兼容,查看 较旧的高阶 API 段落。

  • subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None, **other_popen_kwargs)

  • 运行被 arg 描述的指令。等待指令完成,然后返回一个 CompletedProcess 实例。

    以上显示的参数仅仅是最简单的一些,下面 常用参数 描述(因此在缩写签名中使用仅关键字标示)。完整的函数头和 Popen 的构造函数一样,此函数接受的大多数参数都被传递给该接口。(timeout, input, check 和 capture_output 除外)。

    如果 capture_output 设为 true,stdout 和 stderr 将会被捕获。在使用时,内置的 Popen 对象将自动用 stdout=PIPE 和 stderr=PIPE 创建。stdout 和 stderr 参数不应当与 capture_output 同时提供。如果你希望捕获并将两个流合并在一起,使用 stdout=PIPE 和 stderr=STDOUT 来代替 capture_output。

    timeout 参数将被传递给 Popen.communicate()。如果发生超时,子进程将被杀死并等待。 TimeoutExpired 异常将在子进程中断后被抛出。

    input 参数将被传递给 Popen.communicate() 以及子进程的标准输入。 如果使用此参数,它必须是一个字节序列。 如果指定了 encoding 或 errors 或者将 text 设置为 True,那么也可以是一个字符串。 当使用此参数时,在创建内部 Popen 对象时将自动带上 stdin=PIPE,并且不能再手动指定 stdin 参数。

    如果 check 设为 True, 并且进程以非零状态码退出, 一个 CalledProcessError 异常将被抛出. 这个异常的属性将设置为参数, 退出码, 以及标准输出和标准错误, 如果被捕获到.

    如果 encoding 或者 error 被指定, 或者 text 被设为 True, 标准输入, 标准输出和标准错误的文件对象将通过指定的 encoding 和 errors 以文本模式打开, 否则以默认的 io.TextIOWrapper 打开. universal_newline 参数等同于 text 并且提供了向后兼容性. 默认情况下, 文件对象是以二进制模式打开的.

    如果 env 不是 None, 它必须是一个字典, 为新的进程设置环境变量; 它用于替换继承的当前进程的环境的默认行为. 它将直接被传递给 Popen.

    例如:

    >>>

    >>> subprocess.run(["ls", "-l"])  # doesn't capture outputCompletedProcess(args=['ls', '-l'], returncode=0)>>> subprocess.run("exit 1", shell=True, check=True)Traceback (most recent call last):
      ...subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1>>> subprocess.run(["ls", "-l", "/dev/null"], capture_output=True)CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n', stderr=b'')

    3.5 新版功能.

    在 3.6 版更改: 添加了 encoding 和 errors 形参.

    在 3.7 版更改: 添加了 text 形参, 作为 universal_newlines 的一个更好理解的别名. 添加了 capture_output 形参.

  • class subprocess.CompletedProcess

  • run() 的返回值, 代表一个进程已经结束.

    • args

    • 被用作启动进程的参数. 可能是一个列表或字符串.

    • returncode

    • 子进程的退出状态码. 通常来说, 一个为 0 的退出码表示进程运行正常.

      一个负值 -N 表示子进程被信号 N 中断 (仅 POSIX).

    • stdout

    • 从子进程捕获到的标准输出. 一个字节序列, 或一个字符串, 如果 run() 是设置了 encoding, errors 或者 text=True 来运行的. 如果未有捕获, 则为 None.

      如果你通过 stderr=subprocess.STDOUT 运行进程,标准输入和标准错误将被组合在这个属性中,并且 stderr 将为 None。

    • stderr

    • 捕获到的子进程的标准错误. 一个字节序列, 或者一个字符串, 如果 run() 是设置了参数 encoding, errors或者 text=True 运行的. 如果未有捕获, 则为 None.

    • check_returncode()

    • 如果 returncode 非零, 抛出 CalledProcessError.


Popen 构造函数

此模块的底层的进程创建与管理由 Popen 类处理。它提供了很大的灵活性,因此开发者能够处理未被便利函数覆盖的不常见用例。

  • class subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, group=None, extra_groups=None, user=None, umask=-1, encoding=None, errors=None, text=None)

  • 在一个新的进程中执行子程序。在 POSIX,此类使用类似于 os.execvp() 的行为来执行子程序。在 Windows,此类使用了 Windows CreateProcess() 函数。 Popen 的参数如下:

    args 应当是一个程序参数的序列或者是一个单独的字符串或 path-like object。 默认情况下,如果 args 是序列则要运行的程序为 args 中的第一项。 如果 args 是字符串,则其解读依赖于具体平台,如下所述。 请查看 shell 和 executable 参数了解其与默认行为的其他差异。 除非另有说明,否则推荐以序列形式传入 args。

    向外部函数传入序列形式参数的一个例子如下:

    Popen(["/usr/bin/git", "commit", "-m", "Fixes a bug."])

    在 POSIX,如果 args 是一个字符串,此字符串被作为将被执行的程序的命名或路径解释。但是,只有在不传递任何参数给程序的情况下才能这么做。

    注解

     

    将 shell 命令拆分为参数序列的方式可能并不很直观,特别是在复杂的情况下。 shlex.split() 可以演示如何确定 args 适当的拆分形式:

    >>>

    >>> import shlex, subprocess>>> command_line = input()/bin/vikings -input eggs.txt -output "spam spam.txt" -cmd "echo '$MONEY'">>> args = shlex.split(command_line)>>> print(args)['/bin/vikings', '-input', 'eggs.txt', '-output', 'spam spam.txt', '-cmd', "echo '$MONEY'"]>>> p = subprocess.Popen(args) # Success!

    特别注意,由 shell 中的空格分隔的选项(例如 -input)和参数(例如 eggs.txt )位于分开的列表元素中,而在需要时使用引号或反斜杠转义的参数在 shell (例如包含空格的文件名或上面显示的 echo 命令)是单独的列表元素。

    在 Windows,如果 args 是一个序列,他将通过一个在 在 Windows 上将参数列表转换为一个字符串 描述的方式被转换为一个字符串。这是因为底层的 CreateProcess() 只处理字符串。

    在 3.6 版更改: 在 POSIX 上如果 shell 为 False 并且序列包含路径类对象则 args 形参可以接受一个 path-like object。

    在 3.8 版更改: 如果在Windows 上 shell 为 False 并且序列包含字节串和路径类对象则 args 形参可以接受一个 path-like object。

    参数 shell (默认为 False)指定是否使用 shell 执行程序。如果 shell 为 True,更推荐将 args 作为字符串传递而非序列。

    在 POSIX,当 shell=True, shell 默认为 /bin/sh。如果 args 是一个字符串,此字符串指定将通过 shell 执行的命令。这意味着字符串的格式必须和在命令提示符中所输入的完全相同。这包括,例如,引号和反斜杠转义包含空格的文件名。如果 args 是一个序列,第一项指定了命令,另外的项目将作为传递给 shell (而非命令) 的参数对待。也就是说, Popen 等同于:

    Popen(['/bin/sh', '-c', args[0], args[1], ...])

    在 Windows,使用 shell=True,环境变量 COMSPEC 指定了默认 shell。在 Windows 你唯一需要指定 shell=True 的情况是你想要执行内置在 shell 中的命令(例如 dir 或者 copy)。在运行一个批处理文件或者基于控制台的可执行文件时,不需要 shell=True。

    注解

     

    在使用 shell=True 之前, 请阅读 Security Considerations 段落。

    bufsize 将在 open() 函数创建了 stdin/stdout/stderr 管道文件对象时作为对应的参数供应:

    • 0 表示不使用缓冲区 (读取与写入是一个系统调用并且可以返回短内容)

    • 1 表示行缓冲(只有 universal_newlines=True 时才有用,例如,在文本模式中)

    • 任何其他正值表示使用一个约为对应大小的缓冲区

    • 负的 bufsize (默认)表示使用系统默认的 io.DEFAULT_BUFFER_SIZE。

    在 3.3.1 版更改: bufsize 现在默认为 -1 来启用缓冲,以符合大多数代码所期望的行为。在 Python 3.2.4 和 3.3.1 之前的版本中,它错误地将默认值设为了为 0,这是无缓冲的并且允许短读取。这是无意的,并且与大多数代码所期望的 Python 2 的行为不一致。

    executable 参数指定一个要执行的替换程序。这很少需要。当 shell=True, executable 替换 args 指定运行的程序。但是,原始的 args 仍然被传递给程序。大多数程序将被 args 指定的程序作为命令名对待,这可以与实际运行的程序不同。在 POSIX, args 名作为实际调用程序中可执行文件的显示名称,例如 ps。如果 shell=True,在 POSIX, executable 参数指定用于替换默认 shell /bin/sh 的 shell。

    在 3.6 版更改: 在POSIX 上 executable 形参可以接受一个 path-like object。

    在 3.8 版更改: 在Windows 上 executable 形参可以接受一个字节串和 path-like object。

    stdin, stdout 和 stderr 分别指定被运行的程序的标准输入、输出和标准错误的文件句柄。合法的值有 PIPE , DEVNULL , 一个存在的文件描述符(一个正整数),一个存在的 文件对象 以及 None。 PIPE 表示应创建一个新的对子进程的管道。 DEVNULL 表示使用特殊的 os.devnull 文件。使用默认的 None,则不进行成定向;子进程的文件流将继承自父进程。另外, stderr 可设为 STDOUT,表示应用程序的标准错误数据应和标准输出一同捕获。