将字符串传递给 subprocess.run 似乎将其用单引号括起来否定了通配符搜索?

Passing string to subprocess.run seems to encase it in single quotes negating a wildcard search?

我的机器上有一组与 PCI 设备 (GPU) 相关的目录。在这些目录中有各种 hwmon 接口。我试图通过 运行 命令 find 每个 PCI 设备的特定路径,例如

$ find /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/0000:02:00.0/0000:03:00.0/hwmon/hwmon* -maxdepth 0

此处通配符将匹配每个 PCI 设备位于 .../hwmon/ 下的单个目录。上面的命令在我的终端中输出以下内容

/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/0000:02:00.0/0000:03:00.0/hwmon/hwmon2

我正尝试在 python 中使用 subprocess.run

自动执行此过程
subprocess.run('find', gpu_pci_device_path + '/hwmon/hwmon*', '-maxdepth', '0', stdout=subprocess.PIPE).stdout.decode('utf-8')

在这里,我已经在变量 gpu_pci_device_path 中找到(我称之为)每个 GPU 的 PCI 设备路径。然后我构建通配符路径的其余部分以传递给 find.

尽管看起来 subprocess 将我根据它产生的错误用单引号构建的路径括起来

find: ‘/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/0000:02:00.0/0000:03:00.0/hwmon/hwmon*’: No such file or directory

因此它否定了我在 find 期间的通配符表达式。 如何在调用 subprocess.run 期间将此通配符表达式提供给 find

我证明正在发生封装并且通配符被否定的证据来自命令的输出

$ find '/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/0000:02:00.0/0000:03:00.0/hwmon/hwmon*' -maxdepth 0
find: ‘/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/0000:02:00.0/0000:03:00.0/hwmon/hwmon*’: No such file or directory

简单(Shell-免费)答案

使用 shell=True 是不好的做法,您没有理由在这里这样做:Python 可以使用标准库功能自行执行 globbing。

subprocess.run((['find'] + 
                glob.glob(gpu_pci_device_path + '/hwmon/hwmon*') +
                ['-maxdepth', '0']),
               stdout=subprocess.PIPE).stdout.decode('utf-8')

这与真实的 shell 行为 相当 不同:如果没有路径匹配,这只会使 find 的第一个参数成为 -maxdepth,shell 只有在设置了非默认 nullglob 选项的情况下才会这样做。要解决此问题,请参阅下文。


较长(Shell-兼容)答案

为了更接近地模拟 shell 的行为,我们可能会在 glob.glob() 周围添加一个包装器,使默认语义更 POSIX-y,并支持 bash 的 failglobnullglob 选项(在 bash 中可以使用 shopt 启用)以在找不到匹配文件时调整行为,并且 globstar 使 ** 可以选择性地递归:

import errno
import glob
import os
import subprocess

def glob_or_literal(expression, nullglob=False, failglob=False, globstar=False):
    literal_result = glob.glob(expression, recursive=globstar)
    if literal_result or nullglob:
        return literal_result
    if failglob:
        raise FileNotFoundError(errno.ENOENT, os.strerr(errno.ENOENT), expression)
    return [expression]

subprocess.run((['find'] + 
                glob_or_literal(gpu_pci_device_path + '/hwmon/hwmon*') +
                ['-maxdepth', '0']),
               stdout=subprocess.PIPE).stdout.decode('utf-8')

...在这种情况下,如果不存在 hwmon 目录,您将获得传递给 find 的文字 glob 表达式,让它写出适当有用的错误消息(就像 POSIX-family shells do by default), unless you set the nullglob option (instructing the function, or a shell, to make a non-matching glob not expand to任何参数——可能有不需要的行为,比如 GNU find 决定在当前目录中开始搜索),或者 failglob 选项(这使得 shell,或者在这种情况下glob_or_literal Python 函数,在开始 find 之前中止)。