Как найти каталоги, содержащие только один файл?
Кто-нибудь знает, как искать в тысячах подкаталогов все каталоги, которые содержат только 1 файл и не более 1 файла?
Любое предложение о том, какой инструмент использовать или простой фрагмент кода?
6 ответов
В PowerShell это можно сделать одним из следующих способов:
PS> Get-ChildItem -recurse | `
Where {$_.PSIsContainer -and `
@(Get-ChildItem $_.Fullname | Where {!$_.PSIsContainer}).Length -eq 1}
$_.PSIsContainer
возвращает true для dirs и false для файлов. @()
синтаксис гарантирует, что результатом выражения является массив. Если его длина равна 1, то в этом каталоге только один файл. В этом примере также используется вложенный конвейер, например Get-ChildItem $_.Fullname | Where {...}
в первом скрипте где.
Если бы это было в Linux, я бы соблазнился использовать такую команду.
find . -type 'f' -printf '%h\n' | sort | uniq -c
Команда find распечатает имя каталога всех файлов. Затем мы запускаем сортировку, а затем используем опцию -c uniq, чтобы указать количество файлов в каталоге. Если у вас есть счетчик для каждого каталога, достаточно просто выделить каталоги со значением 1.
Если вы предпочитаете выполнять действия с каталогами, сохраняя их в одной строке, вы можете передать результаты через awk в xargs. Например, чтобы удалить каждую папку:
find . -type 'f' -printf '%h\n' | sort | uniq -c | awk '{ if ($1 == "1") printf "%s%c",$2,0 }' | xargs -0 -I {} rm -rf {}
Это выводит каждый каталог со значением 1 в строку с нулевым символом в конце, которая затем может быть принята в качестве аргумента для xargs. Вы используете строку с нулевым символом в конце, так что пробелы будут обрабатываться как положено. В xargs символы {} будут заменены каждым переданным аргументом.
Вот решение Perl (протестировано на Windows):
#!perl
use strict;
use warnings;
use File::Find;
use File::Slurp;
use File::Spec::Functions qw(catfile canonpath rel2abs);
my ($top) = @ARGV;
die "Provide top directory\n" unless defined($top) and length $top;
find(\&wanted, $top);
sub wanted {
my $name = $File::Find::name;
return unless -d $name;
return unless 1 == grep { -f catfile($name, $_) } read_dir $name;
print canonpath(rel2abs $name), "\n";
}
Выход:
C: \ Temp> f. C: \ Temp \ 1 C: \ Temp \ chrome_9999 C: \ Temp \ CR_3E.tmp
Теперь, если вы хотите что-то сделать с этими папками.
$RootFolder = "c:\myfolder"
$FoldersWithOnlyOneFile = Get-ChildItem $RootFolder -Recurse | `
Where {$_.PSIsContainer -and @( Get-ChildItem $_.Fullname | Where {!$_.PSIsContainer}).Length -eq 1 `
-and @( Get-ChildItem $_.Fullname | Where {$_.PSIsContainer}).Length -eq 0 }
Foreach($folder in $FoldersWithOnlyOneFile)
{
$Folder.FullName
Get-ChildItem $Folder.FullName
}
Я думаю, что вы могли бы сделать что-то в несколько строк, используя
File::Find
Что-то вроде этого.
#!/usr/bin/perl
use File::Find;
my $base_dir = '/';
find(
sub {
# do stuff on each file here.
$filename = $File::Find::name;
$dir = $File::Find::dir;
}, $base_dir );
);
РЕДАКТИРОВАТЬ: Мне действительно больше нравится метод поиска Zoredache, но вы пометили это как Perl.
Решение с:
sub wanted {
my $name = $File::Find::name;
return unless -d $name;
return unless 1 == grep { -f catfile($name, $_) } read_dir $name;
print canonpath(rel2abs $name), "\n";
}
Излишне читает каждый каталог, чтобы сосчитать элементы внутри него, а затем снова читает его, когда на самом деле убывает его (как часть File::Find
фреймворк).
Более простое решение - просто спуститься, определяя наличие каждого файла в каталоге, в котором он находится:
my %count = 0;
...
sub wanted {
return unless -f;
$count{$File::Find::dir}++;
}
my @one_file_dirs = sort grep { $count{$_} == 1 } keys %count;