Как найти файлы в каталогах, перечисленных в файле?

--отмечает конец опций. Здесь это лишнее (, так как имя переменной не будет начинаться с -(, за исключением патологических случаев, когда локаль пользователя будет определена с -в классе символов alpha )и в любом случае не добавляется при наличии флагов ), но безвреден. Я предполагаю, что он там, потому что в коде было:

printf ("declare -%s ", i ? flags : "-");

Который печатался быстрее, чем:

if (i)
  printf ("declare -%s ", i ? flags : "-");
else
  printf ("declare ");

По иронии судьбы, в bash-2.01этот код был изменен на :

.
if (pattr == 0 || posixly_correct == 0)
  printf ("declare -%s ", i ? flags : "-");
else if (i)
  printf ("%s -%s ", this_command_name, flags);
else
  printf ("%s ", this_command_name);

Для повторного использования для exportи readonly, где в тот раз --избегали для export var=value, но не для declare -- var=value.

3
15.08.2019, 18:44
5 ответов

Естественно, публикация вопроса помогла мне избавиться от зацикленности на том, чтобы делать это исключительно с помощью find, и заставила задуматься о расширении файла через Bash. Я публикую ответ, надеясь, что он поможет кому-то еще (, а также задокументирует это для моего собственного будущего использования ).

Заклинание, позволяющее Bash расширить содержимое файла, звучит так: $(<subdirs2search.txt). Итак, если subdirs2search.txt содержит:

SubDir1 SubDir2 SubDir4

Команда, подобная следующей, выполнит нужный поиск:

find $(<subdirs2search.txt) -type f -name="*.txt" -exec grep -H "desired text" {} \;
5
27.01.2020, 21:07

Если имена каталогов состоят из одного -на -строку, вы можете избежать проблем с каталогами, в именах которых есть пробелы, табуляции или подстановочные знаки, используяreadarray(bash v4+):

readarray -t dirs < subdirs2search.txt
find "${dirs[@]}"...

Это все равно не помогло бы, если имена некоторых каталогов начинаются с -, но с GNU findэто невозможно.

9
27.01.2020, 21:07
#!/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можно очень легко модифицировать, чтобы она делала все, что вы хотите, с файлом или с файлом -, например. изменить владельца или разрешения, удалить его, переименовать, вставить или удалить строки или что-то еще, что вы можете захотеть.

напр. если вы хотите удалить файлы, которые содержат текст, соответствующий шаблону, вы можете заменить файловую функцию процесса _чем-то вроде этого:

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). Функции Perl statи lstatобеспечивают доступ ко всем метаданным файлов, которые findмогут использоваться для сопоставления файлов (uid, gid, perms, size, atime, mtime и т. д. ), поэтому wantedфункция может реплицировать ЛЮБОЙ и все предикаты поиска. подробности см. в perldoc -f statи perldoc -f lstat.

Кстати, сценарий был первоначально сгенерирован find2perl, а затем существенно изменен для )чтения списка каталогов из файла и b )для выполнения grep в коде perl, а не путем разветвления. grepи c )добавляют много комментариев. Производительность должна быть почти идентична find... -exec grep, потому что grep не может открывать файлы или выполнять сопоставление с шаблоном регулярного выражения значительно быстрее, чем это может сделать perl. Это может быть даже быстрее.

Кстати, find2perlраньше включался в perl,но начиная с perl 5.22 он был удален, и теперь его можно найти на CPAN по адресу find2perl

3
27.01.2020, 21:07

I found that it didn't limit the search to only text files

ackчасто является удобным инструментом для рекурсивного -grep. Он по умолчанию ограничивает поиск текстовыми файлами (, определяемыми с помощью эвристики на основе имени файла и содержимого ), и по умолчанию пропускает такие каталоги, как .git/ .svn, что, если вы Разработчик, вероятно, то, что вы хотите.https://beyondgrep.com/.

Он входит в состав большинства дистрибутивов GNU/Linux, поэтому его легко установить. Он написан на Perl (, поэтому его регулярные выражения — это perlрегулярные выражения, аналогичные GNUgrep -P).

ack -- "desired text"  $(<subdirs.txt)

, вероятно, должен делать то, что вы хотите, и его очень легко набирать. Кроме того, он обеспечивает хороший цветной вывод для интерактивного использования.

(Различные способы разделения слова -subdirs.txtна командную строку рассматриваются в других ответах. Возможно, вы захотите просто позволить стандартному разбиению слова оболочки -сделать это или readarrayразбить только на строки, а также заблокировать расширение глобуса.)

5
27.01.2020, 21:07

Другой инструмент, похожий наack(ответ Питера Кордеса ), это ripgrep или rg. Он поддерживает то же использование:

rg -- "desired text" $(<subdirs2search.txt)

По умолчанию он также пропускает двоичные файлы.

3
27.01.2020, 21:07

Теги

Похожие вопросы