Как найти каталоги, содержащие только один файл?

Кто-нибудь знает, как искать в тысячах подкаталогов все каталоги, которые содержат только 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;
Другие вопросы по тегам