Acoplar un directorio anidado

71

Esto es probablemente muy simple, pero no puedo entenderlo. Tengo una estructura de directorio como esta (dir2 está dentro de dir1):

/dir1
    /dir2 
       |
        --- file1
       |
        --- file2

¿Cuál es la mejor manera de 'aplanar' esta estructura de director de tal manera de obtener file1 y file2 en dir1 no dir2.

Tortuga
fuente

Respuestas:

75

Puedes hacer esto con GNU findy GNU mv:

find /dir1 -mindepth 2 -type f -exec mv -t /dir1 -i '{}' +

Básicamente, la forma en que funciona si findpasa por todo el árbol de directorios y para cada archivo ( -type f) que no está en el directorio de nivel superior ( -mindepth 2), ejecuta una mvpara moverlo al directorio que desee ( -exec mv … +). El -targumento para le mvpermite especificar primero el directorio de destino, que es necesario porque la +forma de -execcoloca todas las ubicaciones de origen al final del comando. El -ihace mvpreguntar antes de sobrescribir cualquier duplicado; puede sustituir -fsobrescribirlos sin preguntar (o -nno preguntar o sobrescribir).

Como señala Stephane Chazelas, lo anterior solo funciona con herramientas GNU (que son estándar en Linux, pero no en la mayoría de los otros sistemas). Lo siguiente es algo más lento (porque invoca mvvarias veces) pero mucho más universal:

find /dir1 -mindepth 2 -type f -exec mv -i '{}' /dir1 ';'
derobert
fuente
3
Editado para usar de -exec +modo que no ejecute una gran cantidad de procesos demv
Random832
1
@ Random832 Y va a revertir nuevamente, porque + no funciona. mvnecesita el destino como argumento final, pero + tendría las fuentes como argumento final. Find incluso no acepta la sintaxis a la que la cambiaste ( find: missing argument to `-exec')
derobert
1
@ Random832 pero supongo que mvtiene un -tque podemos usar, así que lo cambiaré por eso.
derobert
1
@Dom findimprime archivos ocultos (punto) de forma predeterminada. La profundidad es relativa al directorio que pasa para buscar.
derobert
1
O find ./dir -mindepth 2 -type f -exec mv -f '{}' ./dir ';'si se
anulan los
33

En zsh:

mv dir1/*/**/*(.D) dir1

**/recorre subdirectorios de forma recursiva. El calificador global solo . coincide con los archivos normales y Dgarantiza que se incluyan los archivos de puntos (de forma predeterminada, los archivos cuyo nombre comienza con a .se excluyen de las coincidencias con comodines). Para limpiar los directorios ahora vacíos después, ejecute rmdir dir1/**/*(/Dod): /restringe a los directorios y odordena primero la profundidad de las coincidencias para eliminarlas dir1/dir2/dir3antes dir1/dir2.

Si la longitud total de los nombres de archivo es muy grande, puede encontrar una limitación en la longitud de la línea de comando. Zsh tiene incorporados para mvy rmdirque no se ven afectados por esta limitación: ejecutar zmodload zsh/filespara habilitarlos.

Con solo herramientas POSIX:

find dir1 -type f -exec mv {} dir1 \;
find dir1 -depth -exec rmdir {} \;

o (más rápido porque no tiene que ejecutar un proceso separado para cada archivo)

find dir1 -type f -exec sh -c 'mv "$@" dir1' _ {} +
find dir1 -depth -exec rmdir {} +
Gilles 'SO- deja de ser malvado'
fuente
1
¡Esta debería ser la respuesta aceptada! Especialmente con la versión concisa de zsh.
Adamski
3

Intenta hacer esto:

cp /dir1/dir2/file{1,2} /another/place

o para cada archivo que coincida file[0-9]*en el subdirectorio:

cp /dir1/dir2/file[0-9]* /another/place

Ver http://mywiki.wooledge.org/glob

Gilles Quenot
fuente
Debería haber indicado esto, pero tengo que usar muchos archivos {}en mi problema real.
tortuga
Vea mi segunda solución
Gilles Quenot
Bingo. Gracias por la ayuda. Esta es definitivamente la mejor solución.
tortuga
2

Escribí dos funciones que pueden usar juntas que hacen exactamente eso, pueden limitar el nivel del directorio agregando un -maxdepth $VALparámetro.

# This scripts flattens the file directory
# Run this script with a folder as parameter:
# $ path/to/script path/to/folder

#!/bin/bash

rmEmptyDirs(){
    local DIR="$1"
    for dir in "$DIR"/*/
    do
        [ -d "${dir}" ] || continue # if not a directory, skip
        dir=${dir%*/}
        if [ "$(ls -A "$dir")" ]; then
            rmEmptyDirs "$dir"
        else
            rmdir "$dir"
        fi
    done
    if [ "$(ls -A "$DIR")" ]; then
        rmEmptyDirs "$DIR"
    fi
}

flattenDir(){
    local DIR="$1"
    find "$DIR" -mindepth 2 -type f -exec mv -i '{}' "$DIR" ';'
}

read -p "Do you wish to flatten folder: ${1}? " -n 1 -r
echo    # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
    flattenDir "$1" &
    rmEmptyDirs "$1" &
    echo "Done";
fi
Bruno
fuente
Hombre, acabo de usar mal tu script al olvidar el argumento del camino, que realmente jodió mi servidor. Ok, yo soy el tipo que copiar cosas de pasta y el mal uso de ellos, pero los hombres, ser prudente y añadir cheques / confirmaciones de scripts que borrar / mover productos alimenticios así ...
dulgan
Whoops! Siento oír eso. Espero que tengas una copia de seguridad ... Agregué una confirmación para protección futura.
Bruno
Gracias @Bruno, así es mucho mejor. Mi servidor todavía se ejecuta sin problemas, comenté la parte "aplanar" para simplemente eliminar directorios vacíos de forma recursiva (y ese fue mi error) raíz, hasta que vi un error que me hizo dejar de ejecutar el script.
dulgan
1

Ampliando la respuesta popular a esta pregunta, ya que tenía un caso de uso para aplanar un directorio que contiene archivos del mismo nombre.

dir1/
├── dir2
   └── file
└── dir3
    └── file

En este caso, la opción -i( --interactive) pasada a mvno produciría el resultado deseado para aplanar la estructura del directorio y manejar los conflictos de nombres. Por lo tanto, simplemente se reemplaza con --backup=t(equivalente a --backup=numbered). Más documentación sobre la opción -b( --backup) disponible en https://www.gnu.org/software/coreutils/manual/coreutils.html#Backup-options .

Resultando en:

find dir1/ -mindepth 2 -type f -exec mv -t dir1/ --backup=t '{}' +

Cuyos rendimientos:

dir1/
├── dir2
├── dir3
├── file
└── file.~1~
Yann Eves
fuente
1

tar y zip tienen la capacidad de incorporar y luego eliminar una estructura de directorio, por lo que pude aplanar rápidamente un directorio anidado con

tar -cvf all.tar *

seguido de mover all.tar a una nueva ubicación y luego

tar -xvf all.tar --strip=4

John
fuente