Об'єднати дерева каталогів 2 в Linux без копіювання?


35

У мене є два дерева каталогів з подібними макетами, тобто

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

Я хотів би об'єднати дерева каталогів dir1 та dir2, щоб створити:

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

Я знаю, що я можу це зробити за допомогою команди "cp", але я хочу перемістити файли замість копіювання, тому що фактичні каталоги, які я хочу об'єднати, дійсно великі і містять багато файлів (мільйонів). Якщо я використовую "mv", я отримую помилку "Файл існує" через суперечливі назви каталогів.

ОНОВЛЕННЯ: Ви можете припустити, що між двома деревами каталогів немає дублікатів файлів.


Ви впевнені, що не існує дублювання імен файлів між двома папками? що ви хочете статися, якщо є дублікати?
Зоредаче

Якщо у вас в буквальному сенсі є мільйони файлів в одному каталозі, вам слід роздивитись розділення файлів на окремі підкаталоги з міркувань продуктивності - хоча це не має значення для власне заданого питання.
DrStalker

Відповіді:


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

Це створило б жорсткі посилання, а не переміщення їх, ви можете переконатися, що вони були переміщені правильно, потім видаліть dir1/і dir2/.


9
Типу. Він фактично не дублює будь-яке використання диска, він просто створює інший вказівник на ту саму частину диска і насправді не "копіює" жодних даних. (Див. En.wikipedia.org/wiki/Hard_links ) Однак, цю операцію потрібно робити один раз на файл. Але це по суті те, що всі ці відповіді закінчуються, оскільки ви не можете просто перемістити один каталог.
Крістофер Карел

1
Оскільки у нього немає накладних витрат на копіювання файлів, це цілком прийнятне рішення.
Tobu

2
Це працює лише в тому випадку, якщо вони знаходяться в одній файловій системі. Чи вдасться rsync з параметром видалити переміщення, якби вони були в одній файловій системі? (тобто, просто змініть інформацію про каталог, але не переміщуйте файл).
Рональд Поттол

1
rsync скопіює, а потім видалить, якщо він проходить через файлові системи.
karmawhore

5
Одне застереження: зробіть --link-destшлях абсолютним або відносно merged/; або він буде копіювати.
Тобу

21

Дивно, що ніхто не зазначив, що cpє варіант -l:

-l, - посилання
       файли жорсткого посилання замість копіювання

Можна зробити щось на кшталт

% mkdir злиття
% cp -rl dir1 / * dir2 / * злиття
% rm -r dir *
% злиття дерева 
злиття
├── а
│ ├── file1.txt
│ ├── файл2.txt
│ ├── файл5.txt
│ └── файл6.txt
├── b
│ ├── файл3.txt
│ ├── файл7.txt
│ └── файл8.txt
└── c
    ├── file10.txt
    ├── файл4.txt
    └── файл9.txt

13 каталогів, 0 файлів

Це не працює на різних жорстких дисках ...
Алекс Ліч

4
Правильніше сказати, що вона не працює в файлових системах, оскільки файлові системи можуть охоплювати декілька жорстких дисків. Крім того, якщо оп хочеться уникати копіювання файлів, це непогано, що cp -lце не працює в файлових системах.
lvella

2
Ви можете використовувати cp -a(синонім до cp -RPp), щоб зберегти всі атрибути файлів і уникати наступних посилань: тут команда стає cp -al dir1/* dir2/* merge.
трикассе

5

Ви можете використовувати для цього перейменування (також прізвище, з пакета perl). Будьте уважні, що ім'я не обов'язково посилається на команду, яку я описую, поза межами debian / ubuntu (хоча це єдиний портативний файл perl, якщо він вам потрібен).

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

Також у вас є можливість використання vidir (від moreutils) та редагування шляхів до файлів у бажаному текстовому редакторі.


3

Мені подобаються рішення rsync та prename , але якщо ви дійсно хочете змусити mv виконати роботу та

  • ваша знахідка знає -print0і -depth,
  • ваші xargs знають -0,
  • у вас є printf ,

тоді можна обробити велику кількість файлів, які можуть мати випадкові пробіли у своїх іменах, все за допомогою сценарію оболонки у стилі Борна:

#!/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

Ви можете сказати xargs обмежити його введення до нового рядка та пропустити переклад. наприклад, наведені нижче знайдуть і видалять усі ваші торрент-файли в поточній папці, навіть файли з символами unicode або іншим tomfoolery. find . -name '*.torrent' | xargs -d '\n' rm
PRS

2

Груба сила 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

тест робить це

# 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
ОП вказала мільйони файлів, що, ймовірно, порушить цю конструкцію. Крім того, він не буде належним чином обробляти імена файлів з пробілами, новими рядками тощо.
Кріс Джонсен

0

Мені довелося це робити кілька разів для дерев вихідних кодів на різних стадіях розвитку. Моє рішення було використовувати Git наступним чином:

  1. Створіть сховище git та додайте всі файли з dir1.
  2. Здійснити
  3. Видаліть усі файли та скопіюйте файли з dir2
  4. Здійснити
  5. Перегляньте відмінності між двома пунктами прихильності та прийміть обережні рішення щодо того, як я хочу об'єднати результати.

Ви можете уточнити це за допомогою розгалуження тощо, але це загальна ідея. І ви маєте менше страху перед тим, як його заповнити, оскільки у вас є повний знімок кожної держави.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.