【问题标题】:How do I pass a string into subprocess.Popen (using the stdin argument)?如何将字符串传递给 subprocess.Popen(使用 stdin 参数)?
【发布时间】:2010-09-14 21:07:52
【问题描述】:

如果我执行以下操作:

import subprocess
from cStringIO import StringIO
subprocess.Popen(['grep','f'],stdout=subprocess.PIPE,stdin=StringIO('one\ntwo\nthree\nfour\nfive\nsix\n')).communicate()[0]

我明白了:

Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "/build/toolchain/mac32/python-2.4.3/lib/python2.4/subprocess.py", line 533, in __init__
    (p2cread, p2cwrite,
  File "/build/toolchain/mac32/python-2.4.3/lib/python2.4/subprocess.py", line 830, in _get_handles
    p2cread = stdin.fileno()
AttributeError: 'cStringIO.StringI' object has no attribute 'fileno'

显然,cStringIO.StringIO 对象与文件鸭的距离不足以适应 subprocess.Popen。我该如何解决这个问题?

【问题讨论】:

标签: python subprocess stdin


【解决方案1】:

Popen.communicate() 文档:

注意,如果你想发送数据到 进程的标准输入,你需要 创建 Popen 对象 标准输入=管道。同样,得到任何东西 除了结果元组中的 None 之外, 你需要给 stdout=PIPE 和/或 stderr=PIPE 也是。

替换 os.popen*

    pipe = os.popen(cmd, 'w', bufsize)
    # ==>
    pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin

警告使用communicate()而不是 stdin.write()、stdout.read() 或 stderr.read() 以避免死锁 到任何其他操作系统管道缓冲区 填满并阻止孩子 过程。

所以你的例子可以写成如下:

from subprocess import Popen, PIPE, STDOUT

p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)    
grep_stdout = p.communicate(input=b'one\ntwo\nthree\nfour\nfive\nsix\n')[0]
print(grep_stdout.decode())
# -> four
# -> five
# ->

在 Python 3.5+(encoding 为 3.6+)上,您可以使用 subprocess.run,将输入作为字符串传递给外部命令并获取其退出状态,并在一次调用中将其输出作为字符串返回:

#!/usr/bin/env python3
from subprocess import run, PIPE

p = run(['grep', 'f'], stdout=PIPE,
        input='one\ntwo\nthree\nfour\nfive\nsix\n', encoding='ascii')
print(p.returncode)
# -> 0
print(p.stdout)
# -> four
# -> five
# -> 

【讨论】:

  • 这不是一个好的解决方案。特别是,如果您这样做,您将无法异步处理 p.stdout.readline 输出,因为您必须等待整个 stdout 到达。这也是内存效率低下的。
  • @OTZ 有什么更好的解决方案?
  • @Nick T:“更好”取决于上下文。牛顿定律适用于它们适用的领域,但您需要狭义相对论来设计 GPS。见Non-blocking read on a subprocess.PIPE in python
  • 但请注意communicate 的注释:“如果数据量很大或无限,请勿使用此方法”
  • 有人能解释一下命令的每一步都在做什么,以便它们可以应用于其他问题吗?
【解决方案2】:

我想出了这个解决方法:

>>> p = subprocess.Popen(['grep','f'],stdout=subprocess.PIPE,stdin=subprocess.PIPE)
>>> p.stdin.write(b'one\ntwo\nthree\nfour\nfive\nsix\n') #expects a bytes type object
>>> p.communicate()[0]
'four\nfive\n'
>>> p.stdin.close()

还有更好的吗?

【讨论】:

  • @Moe:不鼓励使用stdin.write(),应该使用p.communicate()。看我的回答。
  • 根据子进程文档:警告 - 使用communicate() 而不是 .stdin.write、.stdout.read 或 .stderr.read 以避免由于任何其他操作系统管道缓冲区填满和阻塞子进程。
  • 如果你确信你的 stdout/err 永远不会被填满,我认为这是一个很好的方法(例如,它会进入一个文件,或者另一个线程正在吃它)并且您有无限量的数据要发送到标准输入。
  • 特别是,这样做仍然可以确保标准输入是关闭的,因此如果子进程是一个永远消耗输入的子进程,communicate 将关闭管道并允许进程优雅地结束。
  • @Lucretiel,如果该进程永远消耗标准输入,那么大概它仍然可以永远写入标准输出,所以我们需要完全不同的技术(不能read()来自它,就像@ 987654326@ 即使没有参数也会这样做)。
【解决方案3】:

如果您使用的是 Python 3.4 或更高版本,那么有一个很好的解决方案。使用input 参数而不是stdin 参数,后者接受字节参数:

output_bytes = subprocess.check_output(
    ["sed", "s/foo/bar/"],
    input=b"foo",
)

这适用于 check_outputrun,但不适用于 callcheck_call 出于某种原因。

在 Python 3.7+ 中,您还可以添加 text=True 以使 check_output 将字符串作为输入并返回字符串(而不是 bytes):

output_string = subprocess.check_output(
    ["sed", "s/foo/bar/"],
    input="foo",
    text=True,
)

【讨论】:

  • @vidstige 你说得对,这很奇怪。我会考虑将此作为 Python 错误提交,我看不出为什么 check_output 应该有 input 参数而不是 call 的任何充分理由。
  • 这是 Python 3.4+ 的最佳答案(在 Python 3.6 中使用)。它确实不适用于check_call,但它适用于run。只要您根据文档传递编码参数,它也可以与 input=string 一起使用。
【解决方案4】:

我有点惊讶没有人建议创建管道,在我看来这是将字符串传递给子进程的标准输入的最简单的方法:

read, write = os.pipe()
os.write(write, "stdin input here")
os.close(write)

subprocess.check_call(['your-command'], stdin=read)

【讨论】:

  • ossubprocess 文档都同意您应该更喜欢后者而不是前者。这是一个遗留的解决方案,有一个(稍微不那么简洁的)标准替换;接受的答案引用了相关文档。
  • 我不确定这是否正确,三胞胎。引用的文档说明了为什么很难使用进程创建的管道,但是在这个解决方案中,它创建了一个管道并将其传入。我相信它避免了在进程已经启动后管理管道的潜在死锁问题。
  • os.popen 被弃用,取而代之的是子进程
  • -1:导致死锁,可能会丢失数据。这个功能已经由 subprocess 模块提供。使用它而不是糟糕地重新实现它(尝试写入一个大于 OS 管道缓冲区的值)
  • 你值得最好的好人,谢谢你最简单最聪明的解决方案
【解决方案5】:

我正在使用python3,发现您需要先对字符串进行编码,然后才能将其传递到标准输入:

p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=PIPE)
out, err = p.communicate(input='one\ntwo\nthree\nfour\nfive\nsix\n'.encode())
print(out)

【讨论】:

  • 您不需要特别对输入进行编码,它只需要一个类似字节的对象(例如b'something')。它也会以字节的形式返回错误和输出。如果你想避免这种情况,你可以将universal_newlines=True 传递给Popen。然后它将接受输入作为 str 并将返回 err/out 作为 str 也。
  • 但请注意,universal_newlines=True 也会转换您的换行符以匹配您的系统
  • 如果您使用的是 Python 3,请参阅my answer 以获得更方便的解决方案。
【解决方案6】:

显然 cStringIO.StringIO 对象并没有足够接近 一个适合 subprocess.Popen 的文件鸭子

恐怕不会。管道是一个低级操作系统概念,因此它绝对需要一个由操作系统级文件描述符表示的文件对象。您的解决方法是正确的。

【讨论】:

    【解决方案7】:
    from subprocess import Popen, PIPE
    from tempfile import SpooledTemporaryFile as tempfile
    f = tempfile()
    f.write('one\ntwo\nthree\nfour\nfive\nsix\n')
    f.seek(0)
    print Popen(['/bin/grep','f'],stdout=PIPE,stdin=f).stdout.read()
    f.close()
    

    【讨论】:

    • fyi,tempfile.SpooledTemporaryFile.__doc__ 说:临时文件包装器,专门用于在超过一定大小或需要文件号时从 StringIO 切换到真实文件。
    【解决方案8】:
    """
    Ex: Dialog (2-way) with a Popen()
    """
    
    p = subprocess.Popen('Your Command Here',
                     stdout=subprocess.PIPE,
                     stderr=subprocess.STDOUT,
                     stdin=PIPE,
                     shell=True,
                     bufsize=0)
    p.stdin.write('START\n')
    out = p.stdout.readline()
    while out:
      line = out
      line = line.rstrip("\n")
    
      if "WHATEVER1" in line:
          pr = 1
          p.stdin.write('DO 1\n')
          out = p.stdout.readline()
          continue
    
      if "WHATEVER2" in line:
          pr = 2
          p.stdin.write('DO 2\n')
          out = p.stdout.readline()
          continue
    """
    ..........
    """
    
    out = p.stdout.readline()
    
    p.wait()
    

    【讨论】:

    • 因为shell=True 无缘无故被如此普遍使用,这是一个流行的问题,让我指出在很多情况下Popen(['cmd', 'with', 'args']) 明显优于Popen('cmd with args', shell=True)并让 shell 将命令和参数分解为标记,但不会提供任何有用的东西,同时增加了大量的复杂性,因此也增加了攻击面。
    【解决方案9】:

    在 Python 3.7+ 上执行此操作:

    my_data = "whatever you want\nshould match this f"
    subprocess.run(["grep", "f"], text=True, input=my_data)
    

    您可能希望添加capture_output=True 以获取以字符串形式运行命令的输出。

    在旧版本的 Python 上,将 text=True 替换为 universal_newlines=True

    subprocess.run(["grep", "f"], universal_newlines=True, input=my_data)
    

    【讨论】:

      【解决方案10】:

      注意Popen.communicate(input=s)如果s太大可能会给你带来麻烦,因为显然父进程会在分叉子子进程之前缓冲它,这意味着它需要“两倍”使用那时的记忆(至少根据“引擎盖下”的解释和找到的链接文档here)。在我的特殊情况下,s 是一个生成器,它首先完全扩展,然后才写入stdin,因此在子进程生成之前父进程是巨大的, 并且没有留下任何内存来分叉它:

      File "/opt/local/stow/python-2.7.2/lib/python2.7/subprocess.py", line 1130, in _execute_child self.pid = os.fork() OSError: [Errno 12] Cannot allocate memory

      【讨论】:

        【解决方案11】:
        p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)    
        p.stdin.write('one\n')
        time.sleep(0.5)
        p.stdin.write('two\n')
        time.sleep(0.5)
        p.stdin.write('three\n')
        time.sleep(0.5)
        testresult = p.communicate()[0]
        time.sleep(0.5)
        print(testresult)
        

        【讨论】:

          【解决方案12】:

          这对grep 来说太过分了,但在我的旅程中,我了解了 Linux 命令expect 和python 库pexpect

          • expect:与互动程序对话
          • pexpect:用于生成子应用程序的 Python 模块;控制它们;并响应其输出中的预期模式。
          import pexpect
          child = pexpect.spawn('grep f', timeout=10)
          child.sendline('text to match')
          print(child.before)
          

          使用 pexpect

          可以轻松使用像 ftp 这样的交互式 shell 应用程序
          import pexpect
          child = pexpect.spawn ('ftp ftp.openbsd.org')
          child.expect ('Name .*: ')
          child.sendline ('anonymous')
          child.expect ('Password:')
          child.sendline ('noah@example.com')
          child.expect ('ftp> ')
          child.sendline ('ls /pub/OpenBSD/')
          child.expect ('ftp> ')
          print child.before   # Print the result of the ls command.
          child.interact()     # Give control of the child to the user.
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-07-22
            • 2014-02-13
            • 2011-06-29
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多