Назад | Перейти на главную страницу

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

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