2009-08-17 19 views
8

Estoy tratando de escribir algunos ruby ​​que busquen recursivamente en un directorio determinado para todos los directorios secundarios vacíos y eliminarlos.Ruby: ¿cómo puedo encontrar y eliminar directorios vacíos recursivamente?

¿Pensamientos?

Nota: Me gustaría una versión de script si es posible. Esta es una necesidad práctica y algo que me ayuda a aprender.

+1

pensó primero: sistema "find. -type d | xargs rmdir -p 2>/dev/null" – kch

+0

Solo una nota, no quiero hacer esta operación de una vez desde la línea de comandos. Va a estar en un guión ruby. Lo que tienes arriba es una versión de línea cmd no? –

+0

bueno, es un comando de shell, sí, pero invocado desde dentro de ruby ​​usando 'Kernel.system';) – kch

Respuesta

15

En Ruby:

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

podría explicar el "-" en la línea 3? –

+0

Estoy restando la matriz que contiene las cadenas "." y ".." de la matriz de entradas de directorio, ya que cada directorio contiene esas dos entradas especiales. – kch

+0

también, ¿podría explicar para qué son las "\"? continuación de línea? son ellos necesarios? –

2

¿Por qué no usar shell?

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

Hace exactamente lo que quiere.

+0

Esperaba una versión de script de ruby ​​para ayudar a comprender el trabajo con archivos. Podría invocar lo anterior con 'sh ' de un script de rake, supongo. ¿Tienes una versión de script? –

+0

El comando de script sería exactamente el mismo. Sin embargo, la publicación siguiente tiene una respuesta que te conviene. – 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

Rescatar de cualquier excepción que no sea la mejor idea en el código de producción real. – kch

+0

Normalmente, tendría que estar de acuerdo, pero esto es una especie de situación límite. En cualquier caso, actualicé la declaración de rescate. – xyz

+0

Sí, es una de estas cosas que está bien siempre que sepa lo que está haciendo. Pero un jugador de Google que está accediendo a esta página podría estar buscando solo un guión rápido, o una biblioteca que tendrá un papel importante en una aplicación intensiva del sistema de archivos. Entonces, básicamente, los n00bs obtienen el código seguro por defecto, y las personas que supuestamente saben lo que hacen lo harán bajo su propio riesgo. – kch

0

He probado este script en OS X, pero si usted está en Windows, tendrá que hacer cambios.

Puede encontrar los archivos en un directorio, incluidos los archivos ocultos, con entradas Dir #.

Este código eliminará los directorios que se vuelvan vacíos una vez que haya eliminado los subdirectorios.

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} 

al igual que el primer ejemplo, pero el primer ejemplo no parece manejar el poco recursivo. El orden y el reverso aseguran que primero tratemos con la mayoría de los directorios anidados.

supongo sort.reverse podría escribir como sort {|a,b| b <=> a} para la eficiencia

3

Tienes que eliminar en orden inverso, de lo contrario si usted tiene un directorio foo vacío con una barra de subdirectorio se eliminarán bar, pero no foo.

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

Mirando los ejemplos de kch, dB. y Vishnu anterior, he reunido una sola línea que creo que es una solución más elegante:

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

utilizo '**/' en lugar de '/**/*' para el pegote, que sólo devuelve los directorios, por lo que no tienen para probar si es un directorio más adelante. Estoy usando reverse_each en lugar de sort.reverse.each ya que es más corto y supuestamente más eficiente, de acuerdo con este post. Prefiero Dir.entries(d).size == 2 a (Dir.entries(d) - %w[ . .. ]).empty? porque es un poco más fácil de leer y entender, aunque (Dir.entries(d) - %w[ . .. ]).empty? probablemente funcionaría mejor si tuviera que ejecutar el script en Windows.

He probado esto bastante en Mac OS X y funciona bien, incluso con directorios vacíos recursivos.

+0

Una compilación muy elegante de soluciones. – Shadow

+1

Buen refactor, pero creo que lo menciono. y ... es más intuitivo que el tamaño == 2, así que iría con 'Dir ['lib/** /']. reverse_each {| d | Dir.rmdir d if Dir.entries (d) .sort ==% w (.. ..)} ' – mahemoff

+0

Sí, ese tipo de es más intuitivo. – Adam

Cuestiones relacionadas