【问题标题】:link several Popen commands with pipes用管道链接几个 Popen 命令
【发布时间】:2023-03-17 16:31:01
【问题描述】:

我知道如何使用 cmd = subprocess.Popen 然后 subprocess.communicate 来运行命令。 大多数时候,我使用一个用 shlex.split 标记的字符串作为 Popen 的 'argv' 参数。 以“ls -l”为例:

import subprocess
import shlex
print subprocess.Popen(shlex.split(r'ls -l'), stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE).communicate()[0]

但是,管道似乎不起作用...例如,以下示例返回注意:

import subprocess
import shlex
print subprocess.Popen(shlex.split(r'ls -l | sed "s/a/b/g"'), stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE).communicate()[0]

你能告诉我我做错了什么吗?

谢谢

【问题讨论】:

标签: python command subprocess pipe popen


【解决方案1】:

我想你想在这里实例化两个单独的 Popen 对象,一个用于“ls”,另一个用于“sed”。您需要将第一个 Popen 对象的 stdout 属性作为 stdin 参数传递给第二个 Popen 对象。

例子:

p1 = subprocess.Popen('ls ...', stdout=subprocess.PIPE)
p2 = subprocess.Popen('sed ...', stdin=p1.stdout, stdout=subprocess.PIPE)
print p2.communicate()

如果你有更多命令,你可以继续这样链接:

p3 = subprocess.Popen('prog', stdin=p2.stdout, ...)

有关如何使用子流程的更多信息,请参阅subprocess documentation

【讨论】:

  • 您能否展示一个完整的工作示例,说明如何将三个进程链接在一起。例如,我不确定我需要调用通信多少次以及何时关闭stdinstdout(文档提到关闭stdout,下面的答案显示关闭stdin。谢谢。
  • @Leonid 您应该只需要在最后一个进程上调用通信一次,因为您已经生成了所有这些并将它们链接在一起。因此,如果您有进程p1p2、....、pn,请在最后调用pn.communicate()
【解决方案2】:

我做了一个小函数来帮助管道,希望它有所帮助。它会根据需要链接 Popens。

from subprocess import Popen, PIPE
import shlex

def run(cmd):
  """Runs the given command locally and returns the output, err and exit_code."""
  if "|" in cmd:    
    cmd_parts = cmd.split('|')
  else:
    cmd_parts = []
    cmd_parts.append(cmd)
  i = 0
  p = {}
  for cmd_part in cmd_parts:
    cmd_part = cmd_part.strip()
    if i == 0:
      p[i]=Popen(shlex.split(cmd_part),stdin=None, stdout=PIPE, stderr=PIPE)
    else:
      p[i]=Popen(shlex.split(cmd_part),stdin=p[i-1].stdout, stdout=PIPE, stderr=PIPE)
    i = i +1
  (output, err) = p[i-1].communicate()
  exit_code = p[0].wait()

  return str(output), str(err), exit_code

output, err, exit_code = run("ls -lha /var/log | grep syslog | grep gz")

if exit_code != 0:
  print "Output:"
  print output
  print "Error:"
  print err
  # Handle error here
else:
  # Be happy :D
  print output

【讨论】:

    【解决方案3】:
    """
    Why don't you use shell
    
    """
    
    def output_shell(line):
    
        try:
            shell_command = Popen(line, stdout=PIPE, stderr=PIPE, shell=True)
        except OSError:
            return None
        except ValueError:
            return None
    
        (output, err) = shell_command.communicate()
        shell_command.wait()
        if shell_command.returncode != 0:
            print "Shell command failed to execute"
            return None
        return str(output)
    

    【讨论】:

      【解决方案4】:

      shlex只根据shell规则分割空格,不处理管道。

      但是,它应该以这种方式工作:

      import subprocess
      import shlex
      
      sp_ls = subprocess.Popen(shlex.split(r'ls -l'), stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
      sp_sed = subprocess.Popen(shlex.split(r'sed "s/a/b/g"'), stdin = sp_ls.stdout, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
      sp_ls.stdin.close() # makes it similiar to /dev/null
      output = sp_ls.communicate()[0] # which makes you ignore any errors.
      print output
      

      根据help(subprocess)

      Replacing shell pipe line
      -------------------------
      output=`dmesg | grep hda`
      ==>
      p1 = Popen(["dmesg"], stdout=PIPE)
      p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
      output = p2.communicate()[0]
      

      HTH

      【讨论】:

      • 该示例使用以下行:p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.。我也应该使用它吗?
      • @Leonid 是的。如果您不这样做,p1 将尝试写信,并且不会收到关于不再有人收听的通知。由于您的程序仍然打开此文件,但未读取,因此您遇到了死锁。