¿Combinar 2 árboles de directorios en Linux sin copiar?

35

Tengo dos árboles de directorio con diseños similares, es decir

.
 |-- dir1
 |   |-- a
 |   |   |-- file1.txt
 |   |   `-- file2.txt
 |   |-- b
 |   |   `-- file3.txt
 |   `-- c
 |       `-- file4.txt
 `-- dir2
     |-- a
     |   |-- file5.txt
     |   `-- file6.txt
     |-- b
     |   |-- file7.txt
     |   `-- file8.txt
     `-- c
         |-- file10.txt
         `-- file9.txt

Me gustaría fusionar los árboles de directorios dir1 y dir2 para crear:

 merged/
 |-- a
 |   |-- file1.txt
 |   |-- file2.txt
 |   |-- file5.txt
 |   `-- file6.txt
 |-- b
 |   |-- file3.txt
 |   |-- file7.txt
 |   `-- file8.txt
 `-- c
     |-- file10.txt
     |-- file4.txt
     `-- file9.txt

Sé que puedo hacer esto usando el comando "cp", pero quiero mover los archivos en lugar de copiarlos, porque los directorios reales que quiero fusionar son realmente grandes y contienen muchos archivos (millones). Si uso "mv", aparece el error "El archivo existe" debido a los nombres de directorio en conflicto.

ACTUALIZACIÓN: puede suponer que no hay archivos duplicados entre los dos árboles de directorios.

bajafresh4life
fuente
¿Está seguro de que no hay duplicación de nombres de archivo entre las dos carpetas? ¿Qué quieres que suceda si hay duplicados?
Zoredache
Si literalmente tiene millones de archivos en un solo directorio, entonces debería considerar dividir los archivos en subdirectorios separados por razones de rendimiento, aunque esto es irrelevante para la pregunta real.
DrStalker

Respuestas:

28
rsync -ax --link-dest=dir1/ dir1/ merged/
rsync -ax --link-dest=dir2/ dir2/ merged/

Esto crearía enlaces duros en lugar de moverlos, puede verificar que se movieron correctamente, luego, elimine dir1/y dir2/.

karmawhore
fuente
99
Mas o menos. En realidad, no duplica el uso de ningún disco, simplemente crea otro puntero al mismo trozo de disco y en realidad no 'copia' ningún dato. (Ver en.wikipedia.org/wiki/Hard_links ) Sin embargo, tiene que hacer esa operación una vez por archivo. Pero eso es esencialmente lo que terminan haciendo todas estas respuestas, ya que no puedes mover un solo directorio.
Christopher Karel
1
Como no tiene la sobrecarga io de copiar archivos, esta es una solución perfectamente aceptable.
Tobu
2
Sin embargo, esto solo funciona si están en el mismo sistema de archivos. ¿Se movería rsync con la opción de eliminar si estuvieran en el mismo sistema de archivos? (es decir, simplemente cambie la información del directorio, pero no mueva el archivo).
Ronald Pottol
1
rsync copiará y luego eliminará si atraviesa los sistemas de archivos.
karmawhore
55
Una advertencia: hacer que el --link-destcamino sea absoluto o relativo a merged/; o lo copiará.
Tobu
21

Es extraño que nadie haya notado que cptiene opción -l:

-l, --link
       archivos de enlace duro en lugar de copiar

Puedes hacer algo como

% mkdir merge
% cp -rl dir1 / * dir2 / * fusionar
% rm -r dir *
% fusión de árbol 
unir
├── a
│ ├── archivo1.txt
│ ├── archivo2.txt
│ ├── archivo5.txt
│ └── archivo6.txt
├── b
│ ├── archivo3.txt
│ ├── archivo7.txt
│ └── archivo8.txt
└── c
    ├── file10.txt
    ├── file4.txt
    └── file9.txt

13 directorios, 0 archivos
Maximiliano
fuente
Esto no funciona en diferentes discos duros ...
Alex Leach
44
Es más correcto decir que no funciona en todos los sistemas de archivos, porque los sistemas de archivos pueden abarcar varios discos duros. Además, si lo que quiere es evitar copiar los archivos, es bueno que cp -lno funcione en todos los sistemas de archivos.
lvella
2
Es posible que desee utilizar cp -a(sinónimo de cp -RPp) para mantener todos los atributos de los archivos y evitar los siguientes enlaces simbólicos: aquí se convierte en el comando cp -al dir1/* dir2/* merge.
tricasse
5

Puede usar rename (también conocido como prename, del paquete perl) para eso. Tenga en cuenta que el nombre no necesariamente se refiere al comando que describo fuera de debian / ubuntu (aunque es un solo archivo perl portátil si lo necesita).

mv -T dir1 merged
rename 's:^dir2/:merged/:' dir2/* dir2/*/*
find dir2 -maxdepth 1 -type d -empty -delete

También tiene la opción de usar vidir (de moreutils) y editar las rutas de archivo desde su editor de texto preferido.

Tobu
fuente
3

Me gustan las soluciones de rsync y de nombre , pero si realmente quieres que mv haga el trabajo y

  • tu hallazgo sabe -print0y -depth,
  • sus xargs sabe -0,
  • tienes printf ,

entonces es posible manejar una gran cantidad de archivos que pueden tener espacios en blanco al azar en sus nombres, todos con un script de shell estilo Bourne:

#!/bin/sh

die() {
    printf '%s: %s\n' "${0##*/}" "$*"
    exit 127
}
maybe=''
maybe() {
    if test -z "$maybe"; then
        "$@"
    else
        printf '%s\n' "$*"
    fi
}

case "$1" in
    -h|--help)
        printf "usage: %s [-n] merge-dir src-dir [src-dir [...]]\n" "${0##*/}"
        printf "\n    Merge the <src-dir> trees into <merge-dir>.\n"
        exit 127
    ;;
    -n|--dry-run)
        maybe=NotRightNow,Thanks.; shift
    ;;
esac

test "$#" -lt 2 && die 'not enough arguments'

mergeDir="$1"; shift

if ! test -e "$mergeDir"; then
    maybe mv "$1" "$mergeDir"
    shift
else
    if ! test -d "$mergeDir"; then
        die "not a directory: $mergeDir"
    fi
fi

xtrace=''
case "$-" in *x*) xtrace=yes; esac
for srcDir; do
    (cd "$srcDir" && find . -print0) |
    xargs -0 sh -c '

        maybe() {
            if test -z "$maybe"; then
                "$@"
            else
                printf "%s\n" "$*"
            fi
        }
        xtrace="$1"; shift
        maybe="$1"; shift
        mergeDir="$1"; shift
        srcDir="$1"; shift
        test -n "$xtrace" && set -x

        for entry; do
            if test -d "$srcDir/$entry"; then
                maybe false >/dev/null && continue
                test -d "$mergeDir/$entry" || mkdir -p "$mergeDir/$entry"
                continue
            else
                maybe mv "$srcDir/$entry" "$mergeDir/$entry"
            fi
        done

    ' - "$xtrace" "$maybe" "$mergeDir" "$srcDir"
    maybe false >/dev/null ||
    find "$srcDir" -depth -type d -print0 | xargs -0 rmdir
done
Chris Johnsen
fuente
Puede decirle a xargs que delimite su entrada a nueva línea y omita la traducción. por ejemplo, lo siguiente buscaría y eliminaría todos sus archivos torrent en el directorio actual, incluso aquellos con caracteres Unicode o alguna otra tontería. find . -name '*.torrent' | xargs -d '\n' rm
PRS
2

Fuerza bruta bash

#! /bin/bash

for f in $(find dir2 -type f)
do
  old=$(dirname $f)
  new=dir1${old##dir2}
  [ -e $new ] || mkdir $new
  mv $f $new
done

prueba hace esto

# setup 
for d in dir1/{a,b,c} dir2/{a,b,c,d} ; do mkdir -p $d ;done
touch dir1/a/file{1,2} dir1/b/file{3,4} dir2/a/file{5,6} dir2/b/file{7,8} dir2/c/file{9,10} dir2/d/file11

# do it and look
$ find dir{1,2} -type f
dir1/a/file1
dir1/a/file2
dir1/a/file5
dir1/a/file6
dir1/b/file3
dir1/b/file7
dir1/b/file8
dir1/c/file4
dir1/c/file9
dir1/c/file10
dir1/d/file11
David J. Liszewski
fuente
2
El OP especificó millones de archivos, lo que probablemente romperá esta construcción. Además, no manejará correctamente los nombres de archivo con espacios, líneas nuevas, etc.
Chris Johnsen
0

He tenido que hacer esto varias veces para árboles de código fuente en diferentes etapas de desarrollo. Mi solución fue usar Git de la siguiente manera:

  1. Cree un repositorio git y agregue todos los archivos de dir1.
  2. Cometer
  3. Elimine todos los archivos y copie en archivos de dir2
  4. Cometer
  5. Vea las diferencias entre los dos puntos de compromiso y tome decisiones cuidadosas sobre cómo quiero fusionar los resultados.

Puede refinarlo con ramificaciones, etc., pero esta es la idea general. Y tiene menos miedo de rellenarlo porque tiene una instantánea completa de cada estado.


fuente