我尝试查找*.e*
与另一个文件 ( ) 位于同一目录中的一些文件 ( md.tpr
)。我需要使用以下内容列出它们(以供进一步处理):
find . -name md.tpr -execdir ls *.e* \;
我尝试了此命令的一些变体和其他一些变体(包括单引号传递给的命令-execdir
或将其传递为sh -c 'ls *.e*'
或eval 'ls *.e*'
仅举几例)。当传递给-exec
or时,通配符似乎不起作用-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
$HOME
find ... -execdir ls -d .bashrc .bash_history ... \;
.b*
find -exec
*
-exec
find ... -execdir sh -c 'echo globs: *' \;
答案3
当bash
shell 找不到与给定的通配模式匹配的文件时,它会保留该模式不展开。这会导致在您的find
命令中将ls
未展开的模式*.e*
作为其参数。该ls
实用程序本身不会扩展文件名通配模式,而是依赖 shell 来完成此操作。
这很可能是您最后一个find
命令的问题,该命令似乎只能在您的主目录中正常工作,可能是因为您的主目录包含与模式匹配的文件(.bashrc
例如, matches .b*
)。当模式与当前目录中的任何内容都不匹配时,模式将按ls
原样传递,并且由于ls
本身不会扩展通配模式,因此它将无法列出任何文件。
简而言之,您不能ls
直接使用-execdir
or调用-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
保存每个匹配文件的路径名) )。
内联脚本使用nullglob
和dotglob
选项集运行,以便*.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
,但这可能是一种更方便的编写代码的方式。