2009-08-17 11 views
8

Próbuję napisać trochę ruby, która rekursywnie przeszuka dany katalog dla wszystkich pustych katalogów potomnych i usunie je.Ruby: w jaki sposób rekursywnie znaleźć i usunąć puste katalogi?

Myśli?

Uwaga: jeśli to możliwe, chciałbym otrzymać wersję skryptu. Jest to zarówno praktyczna potrzeba, jak i coś, co pomoże mi się uczyć.

+1

pierwsza myśl: system "znajdź. -type d | xargs rmdir -p 2>/dev/null" – kch

+0

Tylko uwaga, nie chcę wykonywać tej operacji jednym strzałem z linii poleceń. Będzie w rubinowym skrypcie. Co masz powyżej jest wersja wiersza cmd nie? –

+0

cóż, jest to polecenie powłoki, tak, ale wywoływane z poziomu ruby ​​przy użyciu 'Kernel.system';) – kch

Odpowiedz

15

W Ruby:

Dir['**/*']           \ 
    .select { |d| File.directory? d }     \ 
    .select { |d| (Dir.entries(d) - %w[ . .. ]).empty? } \ 
    .each { |d| Dir.rmdir d } 
+0

czy możesz wyjaśnić "-" w linii 3? –

+0

Odejmuję tablicę zawierającą ciągi "." i ".." z tablicy pozycji katalogu, ponieważ każdy katalog zawiera te dwa specjalne wpisy. – kch

+0

również, czy mógłbyś wyjaśnić, do czego służą "\"? kontynuacja linii? czy są potrzebne? –

2

Dlaczego po prostu nie używać powłoki?

znajdowanie. -type d -empty -exec rmdir '{}' \;

Czy dokładnie to, co chcesz.

+0

Miałem nadzieję, że wersja skryptu ruby ​​pomoże uchwycić pracę z plikami. Mogę przywołać powyższe z "sh " ze skryptu rake, który przypuszczam. Masz wersję skryptu? –

+0

Polecenie skryptu będzie dokładnie takie samo. Jednak poniższy post ma odpowiedź, która Ci odpowiada. – koenigdmj

0
Dir.glob('**/*').each do |dir| 
    begin 
    Dir.rmdir dir if File.directory?(dir) 
    # rescue # this can be dangereous unless used cautiously 
    rescue Errno::ENOTEMPTY 
    end 
end 
+0

Uratowanie przed jakimkolwiek wyjątkiem nie jest najlepszym pomysłem w prawdziwym kodzie produkcji. – kch

+0

Zazwyczaj musiałbym się zgodzić, ale jest to sytuacja z pogranicza. W każdym razie zaktualizowałem komunikat ratunkowy. – xyz

+0

Tak, to jedna z tych rzeczy, o ile dobrze wiesz, co robisz. Ale jeden pracownik googlujący, który trafi na tę stronę, może zaglądać do niego tylko po szybki skrypt lub do biblioteki, która będzie odgrywać główną rolę w aplikacji intensywnie korzystającej z systemu plików. Zasadniczo n00bs domyślnie otrzymuje bezpieczny kod, a ludzie, którzy rzekomo wiedzą, co robią, robią to na własne ryzyko. – kch

0

Przetestowałem ten skrypt w Mac OS X, ale jeśli jesteś na Windows, trzeba dokonać zmian.

Pliki można znaleźć w katalogu, w tym w ukrytych plikach, z wpisami Dir #.

Ten kod usunie katalogi, które staną się puste po usunięciu podkatalogów.

def entries(dir) 
    Dir.entries(dir) - [".", ".."] 
end 

def recursively_delete_empty(dir) 
    subdirs = entries(dir).map { |f| File.join(dir, f) }.select { |f| File.directory? f } 
    subdirs.each do |subdir| 
    recursively_delete_empty subdir 
    end 

    if entries(dir).empty? 
    puts "deleting #{dir}" 
    Dir.rmdir dir 
    end 
end 
1
module MyExtensions 
    module FileUtils 
    # Gracefully delete dirs that are empty (or contain empty children). 
    def rmdir_empty(*dirs) 
     dirs.each do |dir| 
     begin 
      ndel = Dir.glob("#{dir}/**/", File::FNM_DOTMATCH).count do |d| 
      begin; Dir.rmdir d; rescue SystemCallError; end 
      end 
     end while ndel > 0 
     end 
    end 
    end 

    module ::FileUtils 
    extend FileUtils 
    end 
end 
2
Dir['/Users/path/Movies/incompleteAnime/foom/**/*']. \ 
select { |d| File.directory? d }. \ 
sort.reverse. \ 
each {|d| Dir.rmdir(d) if Dir.entries(d).size == 2} 

podobnie jak w pierwszym przykładzie, ale pierwszy przykład nie wydaje się, aby obsłużyć rekurencyjną trochę. Sortowanie i rewers zapewniają, że najpierw zajmiemy się najbardziej zagnieżdżonymi katalogami.

Przypuszczam sort.reverse można zapisać jako sort {|a,b| b <=> a} dla efektywności

3

trzeba usuwać w odwrotnej kolejności, w przeciwnym razie, jeśli masz pustego katalogu foo z barem podkatalogu usuniesz poprzeczkę, ale nie foo.

Dir.glob(dir + "/**/*").select { |d| 
    File.directory?(d) 
    }.reverse_each { |d| 
    if ((Dir.entries(d) - %w[ . .. ]).empty?) 
     Dir.rmdir(d) 
    end 
    } 
5

Patrząc na przykłady z kch, dB. Wisznu i powyżej, jakie zebrała jedną wkładkę, która moim zdaniem jest bardziej eleganckie rozwiązanie:

Dir['**/'].reverse_each { |d| Dir.rmdir d if Dir.entries(d).size == 2 } 

używam '**/' zamiast '/**/*' do glob, który zwraca tylko katalogi, więc nie mam aby sprawdzić, czy jest to katalog później. Używam reverse_each zamiast sort.reverse.each, ponieważ jest krótszy i podobno bardziej wydajny, zgodnie z tym post. Wolę od Dir.entries(d).size == 2 do (Dir.entries(d) - %w[ . .. ]).empty?, ponieważ jest to nieco łatwiejsze do odczytania i zrozumienia, chociaż prawdopodobnie działałby lepiej, gdybyś musiał uruchomić skrypt w systemie Windows.

Przetestowałem to całkiem sporo na Mac OS X i działa dobrze, nawet z rekurencyjnymi pustymi katalogami.

+0

Bardzo elegancka kompilacja rozwiązań. – Shadow

+1

Przyjemny refaktor, ale myślę, że wspomniałem. i ... jest bardziej intuicyjny niż rozmiar == 2, więc użyłbym 'Dir ['lib/** /']. reverse_each {| d | Dir.rmdir d jeśli Dir.entries (d) .sort ==% w (. ..)} ' – mahemoff

+0

Tak, ten rodzaj jest bardziej intuicyjny. – Adam

Powiązane problemy