使用glob参数递归匹配文件名

4
我一直在尝试使用 glob.globos.walk 递归地获取与命令行参数 (sys.argv[1]) 匹配的 glob 模式文件列表。问题是,bash (以及许多其他 shell) 会自动将 glob 模式扩展为文件名。

那么标准的 Unix 程序 (例如 grep -R) 是如何做到这一点的呢?我知道它们不是用 Python 写的,但如果这是在 shell 层面发生的,那就无关紧要了,对吗?脚本有办法告诉 shell 不要自动扩展 glob 模式吗?看起来 set -f 可以禁用 globbing,但我不确定如何在足够早的时候运行它。

我看过Use a Glob() to find files recursively in Python?,但那并没有涵盖从命令行参数中获取 glob 模式的实际操作。

谢谢!

编辑:

类似 grep 的 Perl 脚本ack接受 perl 正则表达式作为其参数之一。因此,ack .* 会打印出每个文件的每一行。但是,.* 应该扩展到目录中的所有隐藏文件。我尝试阅读脚本,但我不懂 Perl;它是如何做到这一点的?

3个回答

6

在执行命令之前,shell会进行通配符扩展。像grep这样的程序不会做任何阻止通配符扩展的事情:它们无法阻止。作为这些程序的调用者,您必须告诉shell您想将特殊字符(例如*?)传递给程序,而不是让shell解释它们。您可以通过将它们放在引号中来实现:

grep -E 'ba(na)* split' *.txt

在所有名为 <something>.txt 的文件中查找 ba splitbana split 等内容。在这种情况下,单引号或双引号都可以解决问题。在单引号之间,shell 不会展开任何内容。在双引号之间,$`\ 仍然会被解释。您还可以通过在字符前面加上反斜杠来保护 shell 不展开单个字符。不仅通配符字符需要保护;例如,在上面的模式中,由于空格用引号括起来,因此它是 grep 的参数之一,而不是参数分隔符。以上代码段的替代编写方式包括:

grep -E "ba(na)* split" *.txt
grep -E ba\(na\)\*\ split *.txt

大多数 shell,如果一个参数包含通配符但模式不匹配任何文件,那么该模式将保持不变并传递给底层命令。因此,像下面这样的命令

grep b[an]*a *.txt

根据系统上存在的文件,grep b[an]*a *.txt会产生不同的效果。如果当前目录中没有以b开头的文件名,则该命令会在文件名匹配为*.txt的文件中搜索模式b[an]*a。如果当前目录包含名为baclavabnmhello.txt的文件,则该命令将扩展为grep baclava bnm hello.txt,因此在两个文件bnmhello.txt中搜索模式baclava。毋庸置疑,依赖此功能来编写脚本是一个坏主意;在命令行中,它偶尔可以省去输入,但是风险很高。

当你在一个不包含点文件的目录中运行ack .*时,shell会运行ack . ..。然后,ack命令的行为是递归地打印出..(当前目录的父目录)下所有文件中的所有非空行(模式.:匹配任何一个字符)。与ack '.*'相比,它在当前目录及其子目录中搜索模式.*(匹配任何内容),因为当你不传递任何文件名参数时,ack的行为就是如此。


请看我的编辑。那么,ack程序如何在没有引号或反斜杠的情况下接受Perl正则表达式呢? - Bryan Head
@Bryan:它并没有,仔细查看输出(或查看我的编辑)。 - Gilles 'SO- stop being evil'

1

是的,set -f,你走对了路。

听起来你要从 shell 调用你的 Python 程序。

每当你使用 shell 发出命令时,它都会尝试扫描 cmd-line 并处理通配符、命令替换和许多其他事情。

所以在你 在命令行上 运行程序之前,必须关闭 globing。

set -f
echo *
*

myprogram *.txt

将字符串“*.txt”传递给您的程序。然后,您可以使用内部的 globbing 来获取您的文件。

或者,您可以通过创建包装脚本来实现基本相同的功能。

 #!/bin/bash
 set -f
 myProgram ${@}

在启动myProgram时,不论是通过命令行、crontab还是通过另一个进程的exec(...)方法,${@}代表你传入的参数。

希望这可以帮到你。


你的意思是在运行程序之前要在shell中显式地运行set -f吗?我想用一个调用set -f的bash脚本来包装Python程序可能行不通...好吧,谢谢! - Bryan Head
刚试了一下,但不幸的是,程序仍然获得了扩展后的文件名。当我用 echo ${@} 替换 myProgram 时,同样的情况发生了;它打印出的是文件名,而不是通配符。 - Bryan Head
是的,${@}会获取来自命令行的参数,$1、$2...$n表示这些值已经展开。因此,在先前的评论中(我现在看不到了),您需要将参数用单引号括起来发送,即myWrapper ... '*'...祝你好运! - shellter

1

当涉及到grep时,它只接受文件名列表,并不会自己进行glob扩展。如果你真的需要将模式作为参数传递,那么必须在命令行上用单引号引用它。但在这样做之前,请考虑让shell执行它设计的工作。


啊,我明白了,这是一个很好的观点。使用grep已经多年了,从来没有注意到它实际上不会处理类似于glob的模式(其他Unix命令也是如此)。嗯,谢谢! - Bryan Head
这符合Unix哲学,即每个工具应该有单独的职责。 - Adam Byrtek

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