subprocess.Popen() 发送 awk 和 grep 行的方式与预期不同
Posted
技术标签:
【中文标题】subprocess.Popen() 发送 awk 和 grep 行的方式与预期不同【英文标题】:subprocess.Popen() sends awk and grep lines differently than expected 【发布时间】:2021-11-02 22:03:24 【问题描述】:在 CentOS 7.2 上,我有一个名为 cpuload 的文件,其中包含以下格式的最新 CPU 负载数据:
last 30 sec:
average load: 0
cpu0 total load: 0
cpu1 total load: 0
cpu2 total load: 0
cpu3 total load: 1
cpu4 total load: 0
cpu5 total load: 0
cpu6 total load: 0
cpu7 total load: 0
last sec:
average load: 1
cpu0 total load: 5
cpu1 total load: 1
cpu2 total load: 1
cpu3 total load: 3
cpu4 total load: 2
cpu5 total load: 1
cpu6 total load: 0
cpu7 total load: 0
我想获得“最后一秒”位的“平均负载:”之后的数字。
当我在终端上将它们作为 shell 命令运行时,两个 cli 命令会为我提供这些信息:
grep 'average load:' cpuload | sed -n 's/.*load: //p' | tail -n1
和
awk 'NR > 2 && /average load:/ print $3' cpuload
但是当我在 subprocess.Popen() 中使用 Shell=True 运行它们时,我只会得到 stderr:
为:
import subprocess
cmd = ["grep", "'average load:'", "cpuload", "|", "sed", "-n", "'s/.*load: //p'", "|", "tail", "-n1"]
test = subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True)
test.stderr.read()
我明白了:
b"Usage: grep [OPTION]... PATTERN [FILE]...\nTry 'grep --help' for more information.\n"
对于:
import subprocess
cmd = cmd = ["awk", "'NR > 2 && /average load:/ print $3'", "cpuload"]
test = subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
test.stderr.read()
我也明白了:
b"awk: cmd. line:1: 'NR > 2 && /average load:/ print $3'\nawk: cmd. line:1: ^ invalid char ''' in expression\n"
尽管我避免使用 |
或者如果shell=True
我得到:
b"Usage: awk [POSIX or GNU style options] -f progfile [--] file ...\nUsage: awk [POSIX or GNU style options] [--] 'program' file ...\nPOSIX options:\t\tGNU long options: (standard)\n\t-f progfile\t\t--file=progfile\n\t-F fs\t\t\t--field-separator=fs\n\t-v var=val\t\t--assign=var=val\nShort options:\t\tGNU long options: (extensions)\n\t-b\t\t\t--characters-as-bytes\n\t-c\t\t\t--traditional\n\t-C\t\t\t--copyright\n\t-d[file]\t\t--dump-variables[=file]\n\t-e 'program-text'\t--source='program-text'\n\t-E file\t\t\t--exec=file\n\t-g\t\t\t--gen-pot\n\t-h\t\t\t--help\n\t-L [fatal]\t\t--lint[=fatal]\n\t-n\t\t\t--non-decimal-data\n\t-N\t\t\t--use-lc-numeric\n\t-O\t\t\t--optimize\n\t-p[file]\t\t--profile[=file]\n\t-P\t\t\t--posix\n\t-r\t\t\t--re-interval\n\t-S\t\t\t--sandbox\n\t-t\t\t\t--lint-old\n\t-V\t\t\t--version\n\nTo report bugs, see node `Bugs' in `gawk.info', which is\nsection `Reporting Problems and Bugs' in the printed version.\n\ngawk is a pattern scanning and processing language.\nBy default it reads standard input and writes standard output.\n\nExamples:\n\tgawk ' sum += $1 ; END print sum ' file\n\tgawk -F: ' print $1 ' /etc/passwd\n"
我做错了什么?
【问题讨论】:
invalid char ''' in expression
似乎很清楚。当然,你可以在 python 中做任何你想做的事情,而不是产生一个子shell来调用其他工具?
【参考方案1】:
我有一个名为 cpuload 的文件,其中包含最新的 CPU 负载数据...我想获取“last sec”位的“average load:”之后的数字
为什么不直接使用简单的 Python 代码来获得您正在寻找的价值?
with open('cpuload') as f:
lines = [l.strip() for l in f.readlines()]
got_it = False
for line in lines:
if got_it:
parts = line.split(':')
result = parts[-1].strip()
print(result)
break
if line == 'last sec:':
got_it = True
输出
1
【讨论】:
这不需要将整个文件读入内存,并且会进行许多可以避免的布尔检查。lines = itertools.dropwhile(lambda x: x != "last sec:", (l.strip() for l in f)); next(lines); parts = next(lines).split(':'); ...
.
我同意。如果它很小,那并不重要。对于更大的,它可以产生影响。
检查 CPU 负载并写入文件的实用程序是一个 C 编写的软件,它是我公司在这台机器上的较大产品的一部分。它更接近机器的“裸机”,并且比 python 运行得更快、更高效。
如果文件不是很大 - 我看不出有什么理由不使用 python。
好吧,问题不在于这个。如果您可以解释为什么 cli 命令有效但 subprocess.Popen() 版本无效,那么它将更具教育意义和帮助。如果迫在眉睫,我将使用类似的东西(尽管我会寻找类似于流的东西而不是将文件倒入内存中。【参考方案2】:
grep
、sed
、tail
... 和管道的第一个案例。
您需要为 Popen
方法使用 shell = True
参数,并为命令使用单个字符串。我们需要在参数周围加上 cotes:
import subprocess
cmd = "grep 'average load:' cpuload | sed -n 's/.*load: //p' | tail -n1"
output = ""
test = subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True)
while True:
output += test.stdout.readline().decode("utf-8")
if test.poll() is not None:
break
print("output=<%s>" % (output))
第二种情况,没有管道:
您不需要为 Popen
方法使用 shell = True
参数和为命令使用单个字符串。我们不会在参数周围放置 cotes:
import subprocess
cmd = ["/usr/bin/awk", "NR > 2 && /^average load:/ print $3", "cpuload"]
output = ""
test = subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
while True:
output += test.stdout.readline().decode("utf-8")
if test.poll() is not None:
break
print("output=<%s>" % (output))
【讨论】:
您不仅没有读到我特别提到我在管道代码中使用了 shell=True,而且您的代码 sn-ps 不起作用。他们各自打印出来:output=<>
【参考方案3】:
问题在于将 awk 参数传递给带有 ' 的子进程,详见here
不接受 Ed Morton 的评论,因为它没有具体说明应该做什么。
【讨论】:
以上是关于subprocess.Popen() 发送 awk 和 grep 行的方式与预期不同的主要内容,如果未能解决你的问题,请参考以下文章
使用 subprocess.Popen 通过 SSH 或 SCP 发送密码
subprocess.Popen 解析命令列表出现问题;管道未正确发送
python subprocess.popen stdin.write
subprocess.Popen("echo $HOME"... 和 subprocess.Popen(["echo", "$HOME"]