Python的subprocess模块与Unix shell返回不同结果

5 投票
4 回答
2074 浏览
提问于 2025-04-16 00:42

我想用Python获取一个文件夹里所有CSV文件的列表。在Unix系统中,这个操作非常简单:

ls -l *.csv

这样做后,我能得到一个只包含以.csv结尾的文件列表。不过,当我用Python的Subprocess模块尝试做同样的事情时:

>>> import subprocess as sp
>>> sp.Popen(["ls", "-l", "*.csv"], stdout = sp.PIPE)
<subprocess.Popen object at 0xb780e90c>
>>> ls: cannot access *.csv: No such file or directory

有人能帮我解释一下这是怎么回事吗?

补充说明:我加上了 shell = True 后,错误消失了,但我得到的却是文件夹里所有文件的列表,而不仅仅是CSV文件。

4 个回答

1

当你在命令行输入 ls -l *.csv 时,命令行会自动把 *.csv 替换成所有符合这个规则的文件名。也就是说,实际执行的命令可能变成了 ls -l spam.txt eggs.txt ham.py 这样的形式。

其实,ls 命令本身并不理解通配符(比如 *)。所以当你把 *.csv 作为参数传给它时,它会把这个当作一个文件名来处理,但实际上并没有这个文件名的文件。正如 Nick 所说,你可以使用 shell=True 这个参数,让 Python 调用一个命令行来运行子进程,这样命令行就会帮你处理通配符了。

4

为什么不直接用 glob 呢?这样会比“调用外部命令”快很多!

import glob
glob.glob('*.csv')

使用这个方法,你只会得到文件名,而不是像 ls -l 那样提供的所有额外信息。不过,如果你想要更多信息,可以通过对感兴趣的文件使用 os.stat 来获取。

如果你真的需要使用 ls -l,我觉得你应该把它当作一个字符串传递,这样外部命令才能进行必要的星号扩展:

proc = sp.Popen('ls -l *.csv', shell=True, stdout=sp.PIPE)
4

如果你想让它像在命令行中那样工作,你需要加上 shell=True(这可能会因你的系统和命令行而有所不同)。在你的例子中,问题在于当你输入 ls -l *.csv 时,是命令行在解析 * 这个符号,而不是 ls 命令本身。实际上,ls 只是负责格式化结果,而命令行已经完成了找出哪些文件符合 *.csv 的工作。使用子进程时,ls 会把 *.csv 当作一个普通的字符串来处理,去找一个叫这个名字的文件,但显然是找不到的(因为这个名字很难创建)。

你其实应该使用 os.listdir,然后自己筛选出你需要的文件名。

撰写回答