Кто-нибудь знает, как искать в тысячах подкаталогов все каталоги, содержащие только 1 файл и не более 1 файла?
Есть предложения о том, какой инструмент использовать или простой фрагмент кода?
В PowerShell это можно сделать следующим образом:
PS> Get-ChildItem -recurse | `
Where {$_.PSIsContainer -and `
@(Get-ChildItem $_.Fullname | Where {!$_.PSIsContainer}).Length -eq 1}
В $_.PSIsContainer
возвращает true для каталогов и false для файлов. В @()
синтаксис гарантирует, что результатом выражения будет массив. Если его длина равна 1, то в этом каталоге есть только один файл. В этом примере также используется вложенный конвейер, например. Get-ChildItem $_.Fullname | Where {...}
в первом блоке сценария Where.
Вот решение 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
Если бы это было в 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 символы {} будут заменены каждым переданным аргументом.
Я думаю, вы могли бы создать что-нибудь в несколько строк, используя
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.
Теперь, если вы хотите что-то сделать с этими папками.
$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
}
Решение с:
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;