【问题标题】:Python: Executing a shell commandPython:执行 shell 命令
【发布时间】:2015-02-12 03:33:10
【问题描述】:

我需要这样做:

paste file1 file2 file3 > result

我的 python 脚本中有以下内容:

from subprocess import call

// other code here.

// Here is how I call the shell command

call ["paste", "file1", "file2", "file3", ">", "result"])

很遗憾,我收到了这个错误:

paste: >: No such file or directory.

对此的任何帮助都会很棒!

【问题讨论】:

  • 重定向不是命令的一部分。
  • 在 Python 中很容易模拟 paste 命令,例如:for lines in izip(*files): print "\t".join(map(str.strip, lines))

标签: python subprocess


【解决方案1】:

如果您明智地决定不使用 shell,则需要自己实现重定向。

https://docs.python.org/2/library/subprocess.html 的文档警告您不要使用管道——但是,您不需要:

import subprocess
with open('result', 'w') as out:
    subprocess.call(["paste", "file1", "file2", "file3"], stdout=out)

应该没问题。

【讨论】:

  • 我发现 shlex.split() 函数(在标准库中)对于在使用 subprocess 模块时生成更具可读性的代码很有用。所以我会使用 call(shlex.split('paste this that'), stdout=out) ... 例如。
  • @JimDennis 实际上,不需要引用 &c,更简单的'paste this that'.split() 也可以:-)。但是有些人更喜欢明确地写出列表,如果这是某人喜欢的风格,那么这样做是可以的。
  • 是的,举个例子。如前所述,如果将命令呈现在代码中,就像人们通常在命令行上看到的那样(引用等),然后像 shell 那样拆分(shlex.split),那么代码的可读性和维护性会大大提高。在源代码中手动将它们拆分为列表文字有点容易出错,而且在我看来,更难阅读。
  • @JimDennis,与大多数其他设计错误的软件问题相比,我可能已经为抗击 shell 引用和转义付出了更多的血和泪,所以当我不这样做时,设法小心地重现同样的地狱 不得不并没有让我觉得投资时间的最佳方式:-)。
  • @JimDennis: shlex.split() 可能会产生错误的输出,例如 shlex.split(r'"\$x"') -> ['\\$x'] 但它应该是 ['$x'] - 传递一个列表以避免细微的错误而不是使用 @ 987654328@.
【解决方案2】:

有两种方法。

  1. 使用shell=True:

    call("paste file1 file2 file3 >result", shell=True)
    

    重定向> 是一个shell 功能。因此,您只能在使用 shell 时访问它:shell=True

  2. 保留shell=False并使用python执行重定向:

    with open('results', 'w') as f:
        subprocess.call(["paste", "file1", "file2", "file3"], stdout=f)
    

第二个通常是首选,因为它避免了 shell 的变幻莫测。

讨论

不使用 shell 时,> 只是命令行中的另一个字符。因此,请考虑错误消息:

paste: >: No such file or directory. 

这表明paste 已接收> 作为参数并试图打开该名称的文件。不存在这样的文件。因此消息。

作为 shell 命令行,可以使用该名称创建文件:

touch '>'

如果存在这样的文件,paste 在被 subprocessshell=False 调用时会使用该文件作为输入。

【讨论】:

  • 这可用作快速破解,但不建议用于健壮的编码。特别是天真地遵循此示例的人将编写易受攻击(不安全)的代码。
  • @JimDennis 请查看更完整的答案。
  • 我认为您应该更加强调这里的警告。另外,当我发表评论时,我没有看到您的其余答案。你修改了吗?我的 cmets 是否与您的编辑竞争?
  • 我在*.com/questions/26831277/… 之前写过一个类似的答案,并根据您的评论对其进行了调整。
【解决方案3】:

如果您不介意在代码库中添加额外的依赖项,您可以考虑安装sh Python 模块(当然,从PyPI:sh 使用pip)。

这是 Python 的 subprocess 模块功能的一个相当巧妙的包装器。使用sh,您的代码将类似于:

#!/usr/bin/python
from sh import paste
paste('file1', 'file2', 'file3', _out='result')

...尽管我认为您希望对此进行一些异常处理,以便您可以使用以下内容:

#!/usr/bin/python
from __future__ import print_function
import sys
from sh import paste
from tempfile import TemporaryFile
with tempfile.TemporaryFile() as err:
    try:
        paste('file1', 'file2', 'file3', _out='result', _err=err)
    except (EnvironmentError, sh.ErrorReturnCode) as e:
        err.seek(0)
        print("Caught Error: %s" % err.read(), file=sys.stderr)

sh 使这些事情变得非常简单,尽管随着你的进步会有一些技巧。您还必须注意 _out= 和该形式的其他关键字参数之间的区别,而 sh 对大多数其他关键字参数的魔力。

sh 的所有魔法都会让任何读过您的代码的人感到困惑。您可能还会发现,使用带有sh 代码的 Python 模块会让您对可移植性问题感到自满。 Python 代码通常具有相当的可移植性,而 Unix 命令行实用程序可能因操作系统而异,甚至因 Linux 发行版或版本而异。以如此透明的方式将大量 shell 实用程序与您的 Python 代码交织在一起可能会使该问题不那么明显。

【讨论】: