【问题标题】:Python3 subprocess outputPython3子进程输出
【发布时间】:2013-08-17 03:17:30
【问题描述】:

我想运行 Linux 字数统计实用程序 wc 来确定 /var/log/syslog 中当前的行数,以便我可以检测到它正在增长。我尝试了各种测试,当我从 wc 中得到结果时,它包括行数和命令(例如,var/log/syslog)。

所以它正在返回: 第1338章 但我只想要行数,所以我想去掉 /var/log/syslog 部分,只保留 1338。

我尝试将它从字节串转换为字符串,然后剥离结果,但没有任何乐趣。转换为字符串以及剥离、解码等的相同故事 - 都无法产生我正在寻找的输出。

这些是我得到的一些示例,系统日志中有 1338 行:

  • b'1338 /var/log/syslog\n'
  • 1338 /var/log/syslog

这是我编写的一些测试代码,试图破解这个问题,但没有解决方案:

import subprocess

#check_output returns byte string
stdoutdata = subprocess.check_output("wc --lines /var/log/syslog", shell=True)
print("2A stdoutdata: " + str(stdoutdata))
stdoutdata = stdoutdata.decode("utf-8")
print("2B stdoutdata: " + str(stdoutdata))    
stdoutdata=stdoutdata.strip()
print("2C stdoutdata: " + str(stdoutdata))    

由此产生的输出是:

  • 2A 标准输出数据:b'1338 /var/log/syslog\n'

  • 2B 标准输出数据:1338 /var/log/syslog

  • 2C 标准输出数据:1338 /var/log/syslog

  • 2D 标准输出数据:1338 /var/log/syslog

【问题讨论】:

标签: python python-3.x subprocess


【解决方案1】:

我建议您使用subprocess.getoutput(),因为它完全符合您的要求——在shell 中运行命令并获取它的string output(而不是byte string 输出)。然后你可以split on whitespace 并从返回的字符串列表中获取第一个元素。

试试这个:

import subprocess
stdoutdata = subprocess.getoutput("wc --lines /var/log/syslog")
print("stdoutdata: " + stdoutdata.split()[0])

【讨论】:

【解决方案2】:

为避免在 *nix 上调用 shell 并解码可能是任意字节序列的文件名('\0' 除外),您可以将文件作为标准输入传递:

import subprocess

with open(b'/var/log/syslog', 'rb') as file:
    nlines = int(subprocess.check_output(['wc', '-l'], stdin=file))
print(nlines)

或者您可以忽略任何解码错误:

import subprocess

stdoutdata = subprocess.check_output(['wc', '-l', '/var/log/syslog'])
nlines = int(stdoutdata.decode('ascii', 'ignore').partition(' ')[0])
print(nlines)

【讨论】:

  • 在这种情况下有什么方法可以得到sys.stdout.encoding,所以我们将它传递给解码而不是ascii?如果我们subprocess.PIPEstdout 怎么办?
  • @Mr_and_Mrs_D 这样做是错误的:1-在一般情况下没有帮助(文件名可能是任何字符编码都无法解码的字节序列,因为它明确表示在答案中。参见 PEP 383) 2- ascii 在这里工作(解码 wc 在 Python 支持的任何语言环境中打印的数字)
【解决方案3】:

从 Python 3.6 开始,您可以通过为 check_output() 提供一个 encoding 参数来返回 str 而不是 bytes

check_output('wc --lines /var/log/syslog', encoding='UTF-8')

但是由于您只需要计数,并且split()int() 都可以与bytes 一起使用,因此您无需费心编码:

linecount = int(check_output('wc -l /var/log/syslog').split()[0])

虽然使用外部程序可能会更容易一些事情(例如,计算journalctl 打印的日志行条目),但在这种特殊情况下,您不需要使用外部程序。最简单的纯 Python 解决方案是:

with open('/var/log/syslog', 'rt') as f:
    linecount = len(f.readlines())

这确实有一个缺点,就是将整个文件读入内存;如果它是一个巨大的文件,请在打开文件之前初始化linecount = 0,并使用for line in f: linecount += 1 循环而不是readlines(),以便在计算时只将文件的一小部分放在内存中。

【讨论】:

  • 谢谢。我无法弄清楚结果开头的 b'... 是什么
  • 通过一次只将一行读入内存来计算行数的好方法是sum(1 for line in f)
【解决方案4】:

相当于Curt J. Sampson的答案也是这个(它返回一个字符串):

subprocess.check_output('wc -l /path/to/your/file | cut -d " " -f1', universal_newlines=True, shell=True)

来自文档:

如果指定了编码或错误,或者文本为真,则文件对象为 stdin、stdout 和 stderr 使用指定的文本模式打开 编码和错误或 io.TextIOWrapper 默认值。这 Universal_newlines 参数等价于文本并提供给 向后兼容性。默认情况下,文件对象以二进制打开 模式。

类似的东西,但使用 subprocess.run() 有点复杂:

subprocess.run(command, shell=True, check=True, universal_newlines=True, stdout=subprocess.PIPE).stdout

因为 subprocess.check_output() 可以等价于 subprocess.run()。

【讨论】:

【解决方案5】:

getoutput(以及更接近的替换 getstatusoutput)不是 check_output 的直接替换 - 3.x 中有一些安全更改阻止以前的一些命令以这种方式工作(我的脚本试图使用iptables 和新命令失败)。更好地适应新的python3输出并添加参数universal_newlines=True:

check_output(command, universal_newlines=True)

此命令的行为与您期望的 check_output 一样,但返回字符串输出而不是字节。它是直接替换。

【讨论】:

    猜你喜欢
    • 2017-07-05
    • 2022-08-03
    • 1970-01-01
    • 2012-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-28
    • 2020-09-26
    相关资源
    最近更新 更多