Unire 2 alberi di directory in Linux senza copiare?


35

Ho due alberi di directory con layout simili, ad es

.
 |-- 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

Vorrei unire gli alberi delle directory dir1 e dir2 per creare:

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

So che posso farlo usando il comando "cp", ma voglio spostare i file invece di copiarli, perché le directory che voglio unire sono davvero grandi e contengono molti file (milioni). Se utilizzo "mv", visualizzo l'errore "Il file esiste" a causa dei nomi di directory in conflitto.

AGGIORNAMENTO: Si può presumere che non ci siano file duplicati tra i due alberi di directory.


Sei sicuro che non ci sia duplicazione di nomi di file tra le due cartelle? cosa vuoi che accada se ci sono duplicati?
Zoredache,

Se hai letteralmente milioni di file in una singola directory, dovresti cercare di dividere i file in sottodirectory separate per motivi di prestazioni - anche se questo è irrilevante per la vera domanda posta.
DrStalker,

Risposte:


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

Ciò creerebbe hardlink anziché spostarli, è possibile verificare che siano stati spostati correttamente, quindi rimuovere dir1/e dir2/.


9
Tipo. In realtà non duplica alcun utilizzo del disco, crea semplicemente un altro puntatore allo stesso pezzo di disco e in realtà non "copia" alcun dato. (Vedi en.wikipedia.org/wiki/Hard_links ) Tuttavia, deve eseguire tale operazione una volta per file. Ma questo è essenzialmente ciò che tutte queste risposte finiscono per fare, dal momento che non puoi semplicemente spostare una singola directory.
Christopher Karel,

1
Dal momento che non ha il sovraccarico di copia dei file, questa è una soluzione perfettamente accettabile.
Tobu,

2
Funziona solo se si trovano sullo stesso file system. Rsync con l'opzione di eliminazione farebbe una mossa se fossero sullo stesso file system? (vale a dire, basta modificare le informazioni sulla directory, ma non spostare il file).
Ronald Pottol,

1
rsync copia, quindi elimina se attraversa i filesystem.
karmawhore,

5
Un avvertimento: rendere il --link-destpercorso assoluto o relativo merged/; o copierà.
Tobu,

21

È strano che nessuno abbia notato che cpha un'opzione -l:

-l, --link
       file con collegamento reale anziché copia

Puoi fare qualcosa del genere

% mkdir merge
% cp -rl dir1 / * dir2 / * merge
% rm -r dir *
% unione albero 
merge
├── a
│ ├── file1.txt
│ ├── file2.txt
│ ├── file5.txt
│ └── file6.txt
├── b
│ ├── file3.txt
│ ├── file7.txt
│ └── file8.txt
└── c
    ├── file10.txt
    ├── file4.txt
    └── file9.txt

13 directory, 0 file

Questo non funziona su diversi dischi rigidi ...
Alex Leach

4
È più corretto affermare che non funziona su più file system, poiché i file system possono estendersi su più dischi rigidi. Inoltre, se l'operazione vuole evitare di copiare i file, è una buona cosa che cp -lnon funziona su tutti i filesystem.
lvella,

2
Puoi usare cp -a(sinonimo di cp -RPp) per mantenere tutti gli attributi dei file ed evitare i seguenti collegamenti simbolici: qui il comando diventa cp -al dir1/* dir2/* merge.
tricasse il

5

A tale scopo puoi usare rename (alias prename, dal pacchetto perl). Attenzione che il nome non si riferisce necessariamente al comando che descrivo al di fuori di debian / ubuntu (anche se è un singolo file perl portatile se ne hai bisogno).

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

Hai anche la possibilità di usare vidir (da moreutils) e di modificare i percorsi dei file dal tuo editor di testo preferito.


3

Mi piacciono le soluzioni rsync e prename , ma se vuoi davvero fare in modo che mv faccia il lavoro e

  • la tua scoperta lo sa -print0e -depth,
  • i tuoi xargs lo sanno -0,
  • hai printf ,

quindi è possibile gestire un gran numero di file che potrebbero avere spazi bianchi casuali nei loro nomi, tutti con uno script shell in stile 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

Puoi dire a xargs di delimitare il suo input a newline e saltare la traduzione. per esempio il seguente troverà ed eliminerebbe tutti i file torrent nella directory corrente, anche quelli con caratteri unicode o qualche altro stupido. find . -name '*.torrent' | xargs -d '\n' rm
PRS,

2

Forza 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

prova fa questo

# 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

2
L'OP ha specificato milioni di file, che probabilmente romperanno questa costruzione. Inoltre, non gestirà correttamente i nomi dei file con spazi, newline, ecc.
Chris Johnsen,

0

Ho dovuto farlo più volte per gli alberi del codice sorgente in diverse fasi di sviluppo. La mia soluzione era usare Git nel modo seguente:

  1. Crea un repository git e aggiungi tutti i file da dir1.
  2. Commettere
  3. Rimuovere tutti i file e copiarli da dir2
  4. Commettere
  5. Visualizza le differenze tra i due punti di commit e prendi decisioni accurate su come voglio unire i risultati.

Puoi raffinarlo con le ramificazioni e così via, ma questa è l'idea generale. E hai meno paura di riempirlo perché hai un'istantanea completa di ogni stato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.