为什么“ls *.e*”不能作为“find -execdir”的参数?

为什么“ls *.e*”不能作为“find -execdir”的参数?

我尝试查找*.e*与另一个文件 ( ) 位于同一目录中的一些文件 ( md.tpr)。我需要使用以下内容列出它们(以供进一步处理):

find . -name md.tpr -execdir ls *.e* \;

我尝试了此命令的一些变体和其他一些变体(包括单引号传递给的命令-execdir或将其传递为sh -c 'ls *.e*'eval 'ls *.e*'仅举几例)。当传递给-execor时,通配符似乎不起作用-execdir。运行上述命令时出现的错误是:

ls: *.e*: No such file or directory

就像一个健全性检查一样,我做了-execdir pwd并且它打印了它应该打印的内容,所以看起来这是一个通配问题,因为这些*.e*文件确实存在于该测试列出的目录中。

现在,我能够以一种不太优雅的方式解决这个问题,但这只是让我困惑为什么通配符和通配符在这里不起作用。有任何想法吗?还是我完全偏离了轨道?

我使用 bash 3.2.25(旧的,但我没有该系统的管理员权限)。

另外,有趣的是,如果我这样做

find ~ -name .bashrc -maxdepth 2 -execdir ls -d .b* \;

它不起作用,除非它是从 完成的$HOME

答案1

find 命令和 shell 都能够文件通配

这是不寻常的 - 大多数命令不能进行通配符,并且完全依赖 shell 来扩展通配符。但 find 是一个超级超级超级用户工具,你很容易用它伤害自己!

示例:当您执行命令时

   find /path -iname *.txt

首先发生的事情是 shell 尝试在当前目录中查找与 *.txt 匹配的所有文件。如果找到的话,它会替换以下名称全部glob 的匹配文件,然后调用 find 命令。查找命令从来没有看到全局如果发生这种情况,则 shell 已将其扩展至不存在。

但是,如果当前目录中没有与 glob 匹配的文件,则 shell 会耸耸肩,然后传递 glob 不变去寻找。因此,此时,find 命令(记住,它理解 glob)将输出它在 /path 下找到的与 glob 匹配的所有文件的名称。

因此,以这种方式使用 glob 意味着 find 的行为会根据当前目录的内容而有所不同。这几乎肯定不是您想要的!

为了防止 shell 在 find 看到它们之前篡改 glob,使用适当的 shell 元字符引用来转义 glob。 通常这只是意味着将你的 globstring 放在单引号中,如下所示

   find /path -iname '*.txt'

请记住,GLOBS 不是正则表达式 - glob“.*”和正则表达式“.*”非常不同,并且不匹配相同的字符串!

答案2

.b*当您键入包含不带引号的全局变量(如或 )的命令时*.e*,shell 将为您扩展该命令。这种事以前find见过。

您的目录中可能有诸如.bashrc、等文件。因此,当从 运行时,您的命令会变成.当从其他地方运行时,该glob 不匹配任何内容,因此它会被传递。这仍然不起作用,因为对全局没有任何作用。如果您希望为 扩展 glob ,则需要调用 shell 来执行此操作:.bash_history$HOME$HOMEfind ... -execdir ls -d .bashrc .bash_history ... \;.b*find -exec*-exec

find ... -execdir sh -c 'echo globs: *' \;

答案3

bashshell 找不到与给定的通配模式匹配的文件时,它会保留该模式不展开。这会导致在您的find命令中将ls未展开的模式*.e*作为其参数。该ls实用程序本身不会扩展文件名通配模式,而是依赖 shell 来完成此操作。

这很可能是您最后一个find命令的问题,该命令似乎只能在您的主目录中正常工作,可能是因为您的主目录包含与模式匹配的文件(.bashrc例如, matches .b*)。当模式与当前目录中的任何内容都不匹配时,模式将按ls原样传递,并且由于ls本身不会扩展通配模式,因此它将无法列出任何文件。

简而言之,您不能ls直接使用-execdiror调用-exec并为其指定文件名通配模式。

你还说你想要列表*.e*匹配“进一步处理”的文件。我强烈建议不要这样做,而是在实际find命令本身中进行该处理。其原因在问题/答案中给出“为什么循环查找的输出是不好的做法?”。

所以,不要考虑你现在正在做的事情

find . -type f -name md.tpr -exec bash -O nullglob -O dotglob -c '
    for pathname do
        dirpath=${pathname%/md.tpr}
        for e in "$dirpath"/*.e*; do
            # process "$e" here!
        done
    done' bash {} +

这是假设md.tpr应该找到一个常规文件。该find命令将找到所有这些文件的路径名md.tpr,并将它们批量提供给内联bash脚本。脚本bash很短:

for pathname do
    dirpath=${pathname%/md.tpr}
    for e in "$dirpath"/*.e*; do
        # process "$e" here!
    done
done

这只是采用给定的参数,提取每个目录的组成部分(通过/md.tpr从路径名中删除我们知道存在的后缀字符串)并循环遍历*.e*每个目录中匹配的文件(依次$e保存每个匹配文件的路径名) )。

内联脚本使用nullglobdotglob选项集运行,以便*.e*模式在不匹配时被完全删除,并且模式将与隐藏名称匹配。

-exec有关使用with 的更多信息find可以在“了解“find”的 -exec 选项”。


既然您已将这个问题标记为,这是如何在普通循环中执行相同的操作bash(这需要bash版本 4 或更高版本):

shopt -s globstar nullglob dotglob

for pathname in ./**/md.tpr; do
    dirpath=${pathname%/md.tpr}
    for e in "$dirpath"/*.e*; do
        # process "$e" here!
    done
done

除了不检查匹配的md.tpr文件是否是常规文件之外,这应该看起来与上面调用的内联脚本非常相似find。 shellglobstar选项bash启用**glob,它“递归地”匹配子目录。

我预计这会比 using 稍微慢一些find,但这可能是一种更方便的编写代码的方式。

相关内容