我有许多需要在我的python脚本中执行的shell命令。我知道我不应该像提到的here那样使用shell = true,并且如果我在所说的here的命令中有管道,我可以使用std输出和输入。
但是问题是我的shell命令非常复杂并且充满了管道,因此我想创建一个通用方法供脚本使用。
我在下面做了一个小测试,但是在打印结果后挂了(我简化了一下,只是放在这里)。有人可以让我知道吗?
谢谢。
PS:这只是一个大型python项目的一小部分,出于商业原因,我尝试这样做。谢谢。
using
答案 0 :(得分:2)
我会要求用户将命令作为单独的实体提供,而不是使用shell字符串并尝试以自己的方式解析它。这样可以避免明显的陷阱来检测|
,它是命令的一部分,而不用作外壳管道。您要求他们以字符串列表或单个字符串的形式提供命令,然后shlex.split
将由您要公开的接口决定。在下面的示例中,为简单起见,我选择了第一个。
一旦有了单独的命令,一个简单的for
循环就足以将前一个命令的输出传递给下一个命令的输入,例如you have found yourself:
def pipe_subprocesses(*commands):
if not commands:
return
next_input = None
for command in commands:
p = subprocess.Popen(command, stdin=next_input, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
next_input = p.stdout
out, err = p.communicate()
if err:
print(err.decode().strip())
else:
print(out.decode())
用法是
>>> pipe_subprocesses(['ls', '-lhtr'], ['awk', '{print $9}'], ['wc', '-l'])
25
现在,这是一种快速且肮脏的方法,可以对其进行设置,并且可以按需运行。但是此代码至少存在两个问题:
为避免这种情况,您需要维护一个已打开进程的列表,并为每个进程明确wait
。而且因为我不知道您的确切用例,所以我只返回失败的第一个进程(如果有)或最后一个进程(如果没有),以便您可以采取相应的行动:
def pipe_subprocesses(*commands):
if not commands:
return
processes = []
next_input = None
for command in commands:
if isinstance(command, str):
command = shlex.split(command)
p = subprocess.Popen(command, stdin=next_input, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
next_input = p.stdout
processes.append(p)
for p in processes:
p.wait()
for p in processes:
if p.returncode != 0:
return p
return p # return the last process in case everything went well
我还以一些shlex
为例,以便您可以混合使用原始字符串和已解析的列表:
>>> pipe_subprocesses('ls -lhtr', ['awk', '{print $9}'], 'wc -l')
25
答案 1 :(得分:2)
不幸的是,在这种情况下,shell为您提供了一些便利,或者Shell完全忽略了您。一些问题:
该函数应该始终wait()
完成每个过程,否则您将获得称为zombie processes的内容。
命令应该使用真实管道相互连接,这样就不需要立即将整个输出读入内存。这是管道的正常工作方式。
每个管道的读取端都应在父进程中关闭,以便子进程可以在下一个进程关闭其输入时正确SIGPIPE
。没有这个,父进程可以使管道保持打开状态,而子进程不知道退出,并且它可能永远运行。
除了SIGPIPE
之外,子流程中的错误应作为异常引发。留给读者练习是在最终过程中提出SIGPIPE
的例外情况,因为在那里{em>不是期望SIGPIPE
,但忽略它并没有害处。
请注意,subprocess.DEVNULL
在Python 3.3之前不存在。我知道有些人仍然使用2.x,您将不得不手动打开/dev/null
的文件,或者只是确定管道中的第一个进程要与{x1}父进程。
代码如下:
stdin
在这里我们可以看到它的实际效果。您可以看到管道正确地以import signal
import subprocess
def run_pipe(*cmds):
"""Run a pipe that chains several commands together."""
pipe = subprocess.DEVNULL
procs = []
try:
for cmd in cmds:
proc = subprocess.Popen(cmd, stdin=pipe,
stdout=subprocess.PIPE)
procs.append(proc)
if pipe is not subprocess.DEVNULL:
pipe.close()
pipe = proc.stdout
stdout, _ = proc.communicate()
finally:
# Must call wait() on every process, otherwise you get
# zombies.
for proc in procs:
proc.wait()
# Fail if any command in the pipe failed, except due to SIGPIPE
# which is expected.
for proc in procs:
if (proc.returncode
and proc.returncode != -signal.SIGPIPE):
raise subprocess.CalledProcessError(
proc.returncode, proc.args)
return stdout
终止(一直运行到yes
),并正确地以SIGPIPE
终止(总是失败)。
false