子进程命令使用ls命令无法找到文件?

6

我正在创建一个程序,它将拉取一系列账号号码,并运行ls -lh命令来查找每个文件。当我在我们的Linux服务器上不使用Python运行命令时,可以轻松找到文件,但是当我通过Python运行时,它会显示找不到它们。

import subprocess as sp
sp.call(['cd', input_dir])
for i, e in enumerate(piv_id_list):
    proc_out = sp.Popen(['ls', '-lh', '*CSV*APP*{0}.zip'.format(e)])
    proc_out_list.append(proc_out)
    print(proc_out)

当我通过Python解释器运行命令时,以下是一些示例输出:

>>> ls: 无法访问 *CSV1000*APP*: 没有那个文件或目录

但是在Linux中,相同的命令:

ls -lh *CSV*APP*

它会返回正确的输出。

4个回答

5
这是因为shell会将通配符与符合模式的现有文件替换。例如,如果您有a.txtb.txt,那么ls *.txt将从shell扩展为ls a.txt b.txt。您实际上要求ls返回关于文件名中包含星号的文件的信息。如果您想进行验证,请使用以下内容:
sp.Popen(['bash', '-c', 'ls', '-lh', '*CSV*APP*{0}.zip'.format(e)])

此外,您应该使用os.chdir来更改目录,因为sp.call(['cd',input_dir])会更改新进程的当前目录,而不是父进程的当前目录。

我应该为所有事情都使用os模块吗?似乎更简单,因为我可以像在Linux中一样直接输入命令字符串。 - flybonzai
2
当然可以。这就是我会做的事情。你不会有任何像ls之类的外部依赖,它可以在其他操作系统上运行,而且也并不困难。 - JuniorCompressor

3

ls,通过Python运行,可能是正确的:我猜测在当前目录中没有名为*CSV*APP*的文件。可能有一个与该通配符模式匹配的文件。但是ls不关心通配符。当您在shell上运行命令时,shell会将通配符扩展为它可以看到的当前目录中的匹配文件名,并且这些扩展名称是shell传递给ls的内容。

要在shell中获得与Python相同的结果(仅用于演示,而不是因为您想要那样做),请使用单引号保护参数免受通配符扩展:

ls -lh '*CVS*APP*'${e}'.zip'

但是如何在Python中获得shell的行为呢?您可以像其他答案建议的那样使用shell=True,但这是一条很滑的路,因为在动态生成的字符串上调用实际的shell(可能取决于更复杂应用程序中的用户输入)可能会使您容易受到命令注入和其他恶意攻击。

在这里,您只需要shell的一个特定行为,即文件名通配符。而Python恰好能够自己完成这个任务:

import subprocess as sp
from glob import glob
sp.call(['cd', input_dir])
for i, e in enumerate(piv_id_list):
    proc_out = sp.Popen(['ls', '-lh', glob('*CSV*APP*{0}.zip'.format(e))])
    proc_out_list.append(proc_out)
    print(proc_out)

正如JuniorCompressor指出的那样,这仍然会在错误的目录中查找,因为cd只会影响cd调用的子进程,因此让我们也修复这个问题:

import subprocess as sp
from glob import glob

os.chdir(input_dir)
for i, e in enumerate(piv_id_list):
    proc_out = sp.Popen(['ls', '-lh', glob('*CSV*APP*{0}.zip'.format(e))])
    proc_out_list.append(proc_out)
    print(proc_out)

你可能可以使用稍微高级的sp.check_output代替直接使用底层的sp.Popen

很抱歉,如果可以的话我会这么做,但是我们公司的工作环境版本只支持2.6。/ - flybonzai
1
@flybonzai:1. 传递cwd参数而不是使用os.chdir()更加局部化(更好)。2. 在Python 2.6上模拟check_output()很容易。3. 您根本不需要调用ls:您可以使用glob模块获取文件名,使用os.path.getsize()获取文件大小或使用os.stat()获取其他任何所需的信息。 - jfs

2
你应该使用 Popencwd 参数shell=True,然后使用 communicate 获取输出。
你的代码应该像这样:
import subprocess as sp
for i, e in enumerate(piv_id_list):
    proc = sp.Popen(['ls', '-lh', '*CSV*APP*{0}.zip'.format(e)], cwd=input_dir, stdout=sp.PIPE, shell=True)
    proc_out_list.append(proc.communicate()[0])
    print(proc_out_list[-1])

但是,为什么要创建一个子进程而不使用标准库呢?

编辑

正如@tripleee所说,它只替换了一些函数。我认为在可能的情况下最好使用内置/标准库;在您的情况下,您“仅”想列出给定模式的文件({{link1:glob}}),并显示关于它们大小的有序信息({{link2:sorted}})({{link3:stat}})。

使用标准库使您的代码更具可移植性;即使您不关心Microsoft Windows的可移植性,您也可能希望避免在没有GNU binutils(即:Mac OS,BSD等)的计算机上运行代码时遇到意外。

对于无法(轻松)用纯Python实现的事情,您需要使用subprocess模块(例如:使用ffmpeg编码视频,使用passwd更改用户密码,使用sudo提升权限等)。


1
我以为subprocess是os模块的替代品? - flybonzai
subprocess 仅取代 os 的一小部分,即 os.popen() 及其直接关联的函数族 (popen2, popen3, 等等)。请参见例如 https://docs.python.org/2/library/os.html#os.popen - tripleee

1
我认为您需要将shell=True作为Popen的参数添加,并用一个字符串替换列表:
proc_out = sp.Popen('ls -lh *CSV*APP*{0}.zip'.format(e), shell=True)

请查看此处有关glob的更多信息和可能的用法:Python subprocess通配符用法


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接