在一个目录中有几个子目录,例如:
TopLevelDir
+-- SubDir1
+-- SubDir2
+-- SubDir3
+-- SubDir4
+-- SubDir5
+-- SubDir6
我需要在这些子目录的特定子集中查找包含某些特定文本的文件。需要什么可以通过以下方式完成:
find SubDir1 SubDir2 SubDir4 -type f -exec grep -H "desired text" {} \;
经常需要类似的搜索。为了节省时间和打字,我想将要搜索的子目录的名称存储在一个文件中,并在下次运行 find 命令时使用它,如下所示:
find subdirs2search.txt -type f -name="*.txt" -exec grep -H "desired text" {} \;
搜索网络,检查手册页,甚至检查我的 *nix 书都没有找到任何关于如何做到这一点的信息。
这可能吗?如果可以的话,该怎么办呢?
答案1
如果目录名称是每行一个,那么您可以使用readarray
(bash v4+) 避免名称中包含空格、制表符或通配符的目录出现问题:
readarray -t dirs < subdirs2search.txt
find "${dirs[@]}" ...
如果某些目录名称以 开头,那仍然没有帮助-
,但对于 GNU 来说find
,没有办法解决这个问题。
答案2
我发现它并没有将搜索限制为仅文本文件
ack
对于递归 grep 类型的东西来说通常是一个方便的工具。它做默认情况下,搜索限制为文本文件(使用基于文件名和内容的启发式确定),并且默认情况下会跳过.git
/等目录.svn
,如果您是开发人员,这可能是您想要的。 https://beyondgrep.com/。
大多数 GNU/Linux 发行版都包含它,因此很容易安装。它是用 perl 编写的(因此它的正则表达式是perl
正则表达式,类似于 GNU 的正则表达式grep -P
)。
ack -- "desired text" $(<subdirs.txt)
应该可以做你想做的事情并且很容易输入。此外,它还可以提供良好的色彩输出以供交互式使用。
(其他答案中介绍了在命令行上进行分词的不同方法subdirs.txt
。您可能只想让 shell 的标准分词来完成此操作,或者readarray
仅在行上进行分词并阻止全局扩展。)
答案3
当然,发布这个问题帮助我摆脱了严格执行此操作的执念,find
并让我想到通过 Bash 扩展文件。我发布一个答案希望它能对其他人有所帮助(并记录下来以供我自己将来使用)。
让 Bash 扩展文件内容的咒语是$(<subdirs2search.txt)
。因此,如果 subdirs2search.txt 包含:
SubDir1 SubDir2 SubDir4
像下面这样的命令将完成所需的搜索:
find $(<subdirs2search.txt) -type f -name="*.txt" -exec grep -H "desired text" {} \;
答案4
#!/usr/bin/perl -w
use strict;
use File::Find ();
sub wanted;
sub process_file ($@);
my $dirfile = shift; # First argument is the filename containing the list
# of directories.
my $pattern = shift; # Second arg is a perl RE containing the pattern to search
# for. Remember to single-quote it on the command line.
# Read in the @dirs array from $dirfile
#
# A NUL-separated file is best, just in case any of the directory names
# contained line-feeds. If you're certain that could never happen, a
# plain-text LF-separated file would do.
#
# BTW, you can easily generate a NUL-separated file from the shell with:
# printf "%s\0" dir1 dir2 dir3 dir4 $'dir\nwith\n3\nLFs' > dirs.txt
my @dirs=();
{
local $/="\0"; # delete this line if you want to use a LF-separated file.
# In that case, the { ... } block around the code from open to
# close is no longer needed. It's only there so it's possible
# to make a local change to the $/ aka $INPUT_RECORD_SEPARATOR
# variable.
open(DIRFILE,"<",$dirfile);
while(<DIRFILE>) {
chomp;
push @dirs, $_;
};
close(DIRFILE);
};
File::Find::find({wanted => \&wanted}, @dirs);
exit;
sub wanted {
my ($dev,$ino,$mode,$nlink,$uid,$gid);
(($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) && -f _ && process_file($_);
}
sub process_file ($@) {
# This function currently just greps for pattern in the filename passed to
# it. As the function name implies, it could be used to process the file
# in any way, not just grep it.
my $filename = shift;
# uncomment the return statement below to skip "binary" files.
# (note this is a workable but fairly crude test. Perl's File::MMagic
# module can be used to more accurately identify file types, using the
# same "magic" file databases as the /usr/bin/file command)
# return if -B $filename;
open(FILE,"<",$filename);
while(<FILE>) {
print "$filename:$_" if (m/$pattern/o) ;
};
close(FILE);
}
这使用perl
perl 的File::Find
模块来完成与您的find ... -exec grep
.
这个脚本没有什么特别有趣或特别的地方除了该process_file
函数可以很容易地修改为您想要对文件执行任何操作 - 例如更改所有者或权限、删除它、重命名它、插入或删除行或您可能想要的任何其他操作。
例如,如果您想删除包含与模式匹配的文本的文件,您可以将 process_file 函数替换为如下所示:
sub process_file ($@) {
my $filename = shift;
my $found = 0;
# uncomment to skip "binary" files:
return if -B $filename;
open(FILE,"<",$filename);
while(<FILE>) {
if (m/$pattern/o) {
$found = 1;
last;
};
};
close(FILE);
unlink $filename if ($found);
}
还值得一提的是,wanted
该脚本中的函数当前仅查找常规文件(-f
测试)。 Perlstat
和lstat
函数提供对可用于匹配文件的所有文件元数据find
(uid、gid、perms、大小、atime、mtime 等)的访问,因此该wanted
函数可以复制任何和所有查找谓词。请参阅perldoc -f stat
和perldoc -f lstat
了解详细信息。
顺便说一句,该脚本最初是由 生成的find2perl
,然后进行了大幅修改,a) 从文件中读取目录列表,b) 在 perl 代码中执行 grep 而不是通过 fork 进行 grep,grep
c) 添加大量注释。性能应该几乎相同,find ... -exec grep
因为 grep 无法比 perl 更快地打开文件或进行正则表达式模式匹配。它甚至可能更快。
另外顺便说一句,find2perl
它曾经包含在 perl 中,但从 perl 5.22 开始它被删除了,现在可以在 CPAN 上找到查找2perl