递归匹配带通配符的文件名
我一直在尝试通过命令行参数(sys.argv[1]
)获取与某个模式匹配的文件列表,想用glob.glob
和os.walk
来实现。问题是,bash(以及其他很多类似的命令行工具)会自动把这种模式扩展成文件名。
那么,标准的Unix程序(比如grep -R
)是怎么做到这一点的呢?我知道它们不是用Python写的,但如果这个扩展是在命令行层面发生的,那应该没关系吧?有没有办法让脚本告诉命令行不要自动扩展这种模式?看起来set -f
可以禁用这种扩展,但我不太确定怎么在合适的时机使用它。
我看到过一篇文章用Glob()在Python中递归查找文件,但那篇文章没有涉及如何从命令行参数中获取这种模式。
谢谢!
编辑:
那个类似grep的perl脚本ack接受一个perl正则表达式作为参数。因此,ack .*
会打印出每个文件的每一行。但.*
应该会扩展为目录中的所有隐藏文件。我试着读这个脚本,但我不懂perl;它是怎么做到的呢?
3 个回答
说到grep,它其实就是接受一系列文件名,而不会自己去处理那些通配符。如果你真的需要把一个模式作为参数传进去,必须在命令行上用单引号把它括起来。不过在你这么做之前,想想是不是可以让命令行的外壳程序来完成它本来应该做的事情。
没错,set -f
,你走在正确的路上。
听起来你是想从命令行调用你的 Python 程序。
每次你在命令行中输入指令时,系统都会扫描你输入的内容,并处理一些特殊符号,比如通配符、命令替换等等。
所以在你运行程序之前,必须先关闭这种特殊处理,特别是在命令行中。
set -f
echo *
*
myprogram *.txt
这样会把字符串 '*.txt' 传递给你的程序。然后你可以在程序内部使用通配符来获取你的文件。
或者你也可以通过创建一个包装脚本来实现类似的功能。
#!/bin/bash
set -f
myProgram ${@}
在这个脚本中,${@} 是你在启动
myProgram`时传入的参数,无论是从命令行、定时任务还是通过 exec(...) 从其他进程调用。
希望这对你有帮助。
在你运行命令之前,shell(命令行的一个部分)会先处理一些特殊字符,比如通配符。像grep这样的程序并不会阻止这些特殊字符被处理,因为它们没办法。你需要告诉shell,你想把这些特殊字符,比如*
和?
,直接传给程序,而不是让shell自己去理解它们。你可以通过把这些字符放在引号里来实现:
grep -E 'ba(na)* split' *.txt
(在所有名为<something>.txt
的文件中查找ba split
、bana split
等)在这种情况下,单引号或双引号都可以。用单引号包裹时,shell不会对里面的内容进行任何处理。用双引号包裹时,$
、`
和\
这些字符还是会被解释。你也可以在一个字符前加上反斜杠来保护它不被shell处理。不仅仅是通配符需要保护;例如,上面提到的模式中的空格也要用引号包起来,这样它才算是grep
的一个参数,而不是参数之间的分隔符。上面代码的另一种写法包括:
grep -E "ba(na)* split" *.txt
grep -E ba\(na\)\*\ split *.txt
在大多数shell中,如果一个参数包含通配符但没有匹配到任何文件,模式会保持不变并传递给底层命令。所以像这样的命令:
grep b[an]*a *.txt
根据系统中存在的文件,效果会有所不同。如果当前目录下没有以b
开头的文件,这个命令会在匹配*.txt
的文件中搜索模式b[an]*a
。如果当前目录下有名为baclava
、bnm
和hello.txt
的文件,这个命令就会变成grep baclava bnm hello.txt
,所以它会在文件bnm
和hello.txt
中搜索模式baclava
。显然,在脚本中依赖这种行为并不是个好主意;在命令行中偶尔可以省点输入,但风险很大。
当你在一个没有点文件的目录中运行ack .*
时,shell会执行ack . ..
。此时,ack
命令的行为是递归地打印出所有非空行(模式.
:匹配任意一个字符)在..
(当前目录的上级目录)下的所有文件中。与之对比的是ack '.*'
,它会在当前目录及其子目录中搜索模式.*
(匹配任何内容),这是因为在没有传递任何文件名参数时,ack
的行为就是这样。