【问题标题】:Find (bash command) doesn't work with subprocess?查找(bash 命令)不适用于子进程?
【发布时间】:2013-11-08 21:03:21
【问题描述】:

我在许多(python-django)模板中重命名了一个 css 类名。然而,css 文件广泛分布在多个目录中的多个文件中。我有一个 python sn-p 从根目录开始重命名,然后递归地重命名所有 css 文件。

from os import walk, curdir
import subprocess

COMMAND = "find %s -iname *.css | xargs sed -i s/[Ff][Oo][Oo]/bar/g"
test_command = 'echo "This is just a test. DIR: %s"'

def renamer(command):
  print command  # Please ignore the print commands.
  proccess = subprocess.Popen(command.split(), stdout = subprocess.PIPE)
  op = proccess.communicate()[0]
  print op

for root, dirs, files in walk(curdir):
  if root:
    command = COMMAND % root
    renamer(command)

它不起作用,给出:

find ./cms/djangoapps/contentstore/management/commands/tests -iname *.css | xargs sed -i s/[Ee][Dd][Xx]/gurukul/g
find: paths must precede expression: |
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]

find ./cms/djangoapps/contentstore/views -iname *.css | xargs sed -i s/[Ee][Dd][Xx]/gurukul/g
find: paths must precede expression: |
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]

当我复制并运行相同的命令(如上所示)时,find 不会出错,并且 sed 要么没有输入文件,要么可以正常工作。

python sn-p 出了什么问题?

【问题讨论】:

  • 您需要使用shell=True 才能使管道正常工作。例如,请参阅this answer
  • 在这种情况下你不需要管道:op = subprocess.check_output(["find", root]+ r"-iname \*.css -exec sed -i s/foo/bar/gi {} +".split())

标签: python sed find subprocess xargs


【解决方案1】:

您不是在尝试运行单个命令,而是在运行多个命令的 shell 管道,并且您尝试在不调用 shell 的情况下执行此操作。那是不可能的。你这样做的方式,| 只是find 的参数之一,这就是为什么find 告诉你它不理解那个“路径必须先于表达式:|”的参数错误。

可以通过将shell=True 添加到您的Popen 来解决此问题。

但更好的解决方案是在 Python 中执行管道并将 shell 排除在外。请参阅文档中的 Replacing Older Functions with the subprocess Module 以获得解释,但我将展示一个示例。

同时,您不应该使用split 来拆分命令行。最好的解决方案是编写单独的参数列表,而不是将它们连接成一个字符串,只是为了将它们分开。如果必须这样做,请使用shlex 模块;这就是它的用途。但在您的情况下,即使这样也无济于事,因为您正在逐字插入随机字符串,其中很容易包含空格或引号,并且没有任何方法——shlex 或其他方式——可以重建第一名。

所以:

pfind = Popen(['find', root, '-iname', '*.css'], stdout=PIPE)
pxargs = Popen(['xargs', 'sed', '-i', 's/[Ff][Oo][Oo]/bar/g'], 
               stdin=pfind.stdout, stdout=PIPE)
pfind.stdout.close()
output = pxargs.communicate()

但这里有一个更好的解决方案。

Python 有os.walkfind 做同样的事情,你可以很容易地模拟xargs,但实际上没有必要这样做,它有自己的re 模块可以使用而不是@987654336 @。那么,为什么不使用它们呢?

或者,相反,bash 在驱动和连接简单命令方面比 Python 好得多,所以如果你宁愿使用 findsed 而不是 os.walkre.sub,为什么要编写驱动脚本首先在 Python 中?

【讨论】:

  • +1 为两者都提供了很好的答案,通过示例展示了正确的方法,并修复了我的尝试。我现在意识到 findos.walk 在我的努力中的重叠,只是可悲地心不在焉。
  • 使用 find 而不是 os.walk 的一个原因是 find 要快得多。 os.walk 对每个文件进行 sys 调用,尽管 python 3.5+ 有一个新函数,os.scandir 使用相同的 sys 调用 find 确实并且具有与 find 更可比的性能
  • @twneale 实际上,scandir 不使用与find 相同的系统调用(至少在现代 GNU 和 BSD 系统上)。那将是fts。 (最后我检查了一下,有一个为 Python 包装 fts 的库,但我遇到了一些问题,所以我用 ctypes 编写了自己的不完整包装器。也许现在有更好的包装器。)在 FreeBSD 上,@987654348 @ 可以快很多;在 linux(至少对于某些文件系统,超前缓存惊人)或 OS X(其中一些关键优化不适用于 Apple 的文件系统)上,scandir 可能没问题。但首先要确保确实存在性能问题……
【解决方案2】:

问题是管道。要将管道与子流程模块一起使用,您必须传递shell=True

【讨论】:

  • abarnert 的回答比我的要准确得多。去吧。
猜你喜欢
  • 2018-03-28
  • 2018-03-09
  • 2016-07-17
  • 1970-01-01
  • 2017-07-14
  • 1970-01-01
  • 2023-03-22
  • 2015-12-12
  • 1970-01-01
相关资源
最近更新 更多