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.


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


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

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.


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

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

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.