Scalić 2 drzewa katalogów w systemie Linux bez kopiowania?

35

Mam dwa drzewa katalogów o podobnych układach, tj

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

Chciałbym scalić drzewa katalogów dir1 i dir2, aby utworzyć:

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

Wiem, że mogę to zrobić za pomocą polecenia „cp”, ale chcę przenieść pliki zamiast kopiować, ponieważ rzeczywiste katalogi, które chcę scalić, są naprawdę duże i zawierają wiele plików (miliony). Jeśli użyję „mv”, pojawia się błąd „Plik istnieje” z powodu sprzecznych nazw katalogów.

AKTUALIZACJA: Możesz założyć, że pomiędzy dwoma drzewami katalogów nie ma duplikatów plików.

bajafresh4life
źródło
Czy na pewno nie ma powielania nazw plików między dwoma folderami? co chcesz zrobić, jeśli są duplikaty?
Zoredache
Jeśli dosłownie masz miliony plików w jednym katalogu, powinieneś rozważyć podzielenie plików na osobne podkatalogi ze względu na wydajność - chociaż nie ma to znaczenia dla zadanego pytania.
DrStalker

Odpowiedzi:

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

Spowoduje to utworzenie linków twardych zamiast ich przenoszenia, możesz sprawdzić, czy zostały one poprawnie przeniesione, a następnie usunąć dir1/i dir2/.

karmawhore
źródło
9
Rodzaj. W rzeczywistości nie powiela żadnego użycia dysku, po prostu tworzy kolejny wskaźnik do tego samego kawałka dysku i nie kopiuje żadnych danych. (Zobacz en.wikipedia.org/wiki/Hard_links ) Jednak musi wykonać tę operację raz na plik. Ale w zasadzie to właśnie robią wszystkie te odpowiedzi, ponieważ nie można po prostu przenieść jednego katalogu.
Christopher Karel
1
Ponieważ nie ma narzutu związanego z kopiowaniem plików, jest to całkowicie akceptowalne rozwiązanie.
Tobu
2
Działa to tylko wtedy, gdy są w tym samym systemie plików. Czy rsync z opcją usuwania wykonałby ruch, gdyby znajdowały się w tym samym systemie plików? (to znaczy, po prostu zmień informacje o katalogu, ale nie przenieś pliku).
Ronald Pottol
1
rsync skopiuje, a następnie usunie, jeśli przejdzie przez systemy plików.
karmawhore
5
Jedno zastrzeżenie: uczynić --link-destścieżkę bezwzględną lub względną merged/; lub skopiuje.
Tobu,
21

Dziwne, że nikt nie zauważył, że cpma opcję -l:

-l, --link
       pliki twardych linków zamiast kopiowania

Możesz zrobić coś takiego

Scalenie% mkdir
Scalenie% cp -rl katalog1 / * katalog2 / *
% rm -r katalog *
% scalania drzew 
łączyć
├── a
├── ├── plik1.txt
2 ├── plik2.txt
├── ├── plik5.txt
└── └── plik6.txt
├── b
├── ├── plik3.txt
├── ├── plik7.txt
└── └── plik8.txt
└── c
    ├── plik 10.txt
    ├── plik4.txt
    └── plik9.txt

13 katalogów, 0 plików
Maksymilian
źródło
To nie działa na różnych dyskach twardych ...
Alex Leach,
4
Bardziej słuszne jest stwierdzenie, że nie działa on w różnych systemach plików, ponieważ systemy plików mogą obejmować wiele dysków twardych. Ponadto, jeśli operacja chce uniknąć kopiowania plików, dobrze, że cp -lnie działa ona w różnych systemach plików.
lvella
2
Możesz użyć cp -a(synonim do cp -RPp), aby zachować wszystkie atrybuty plików i uniknąć następujących dowiązań symbolicznych: tutaj staje się polecenie cp -al dir1/* dir2/* merge.
trójkołowy
5

Możesz do tego użyć zmiany nazwy (inaczej prename, z pakietu perla). Uwaga: nazwa niekoniecznie odnosi się do polecenia, które opisuję poza debian / ubuntu (chociaż jest to pojedynczy przenośny plik perla, jeśli go potrzebujesz).

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

Masz również opcję użycia vidir (z moreutils) i edycji ścieżek plików z preferowanego edytora tekstu.

Tobu
źródło
3

Lubię rozwiązania rsync i prename , ale jeśli naprawdę chcesz zmusić mv do pracy i

  • Twój znalezisko wie -print0i -depth,
  • twój xargs wie -0,
  • masz printf ,

wówczas możliwe jest obsługiwanie dużej liczby plików, które mogą mieć losowe białe spacje w swoich nazwach, wszystkie za pomocą skryptu powłoki w stylu Bourne'a:

#!/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
źródło
Możesz powiedzieć xargs, aby ograniczył jego wejście do znaku nowej linii i pominął tłumaczenie. na przykład poniższe znajdowałyby i usunęły wszystkie twoje pliki torrentów w bieżącym katalogu, nawet te ze znakami Unicode lub innymi błędami. find . -name '*.torrent' | xargs -d '\n' rm
PRS
2

Brutalna siła 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

test to robi

# 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
źródło
2
OP określił miliony plików, które prawdopodobnie zepsują tę konstrukcję. Ponadto nie będzie poprawnie obsługiwał nazw plików ze spacjami, znaków nowej linii itp.
Chris Johnsen,
0

Musiałem to zrobić kilka razy dla drzew kodu źródłowego na różnych etapach rozwoju. Moim rozwiązaniem było użycie Git w następujący sposób:

  1. Utwórz repozytorium git i dodaj wszystkie pliki z katalogu 1.
  2. Popełnić
  3. Usuń wszystkie pliki i skopiuj pliki z katalogu 2
  4. Popełnić
  5. Zobacz różnice między dwoma punktami zatwierdzenia i podejmuj ostrożne decyzje dotyczące sposobu scalenia wyników.

Możesz go wyrafinować za pomocą rozgałęzień i tak dalej, ale to jest ogólna idea. I mniej boisz się go upchnąć, ponieważ masz pełną migawkę każdego stanu.


źródło