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.