Объединить 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», я получаю ошибку «Файл существует» из-за конфликтующих имен каталогов.

ОБНОВЛЕНИЕ: Вы можете предположить, что между двумя деревьями каталогов нет повторяющихся файлов.

bajafresh4life
источник
Вы уверены, что между двумя папками нет дублирования имен файлов? что вы хотите, чтобы произошло, если есть дубликаты?
Зоредаче
Если у вас есть буквально миллионы файлов в одном каталоге, вы должны разбить файлы на отдельные подкаталоги по соображениям производительности - хотя это не имеет отношения к конкретному заданному вопросу.
DrStalker

Ответы:

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

Это будет создавать жесткие ссылки, а не перемещать их, вы можете убедиться, что они были перемещены правильно, а затем удалить dir1/и dir2/.

karmawhore
источник
9
Что-то вроде. На самом деле он не дублирует использование диска, он просто создает другой указатель на тот же кусок диска и фактически не «копирует» какие-либо данные. (См. En.wikipedia.org/wiki/Hard_links ) Однако он должен выполнять эту операцию один раз для каждого файла. Но это, по сути, то, что все эти ответы в конечном итоге делают, так как вы не можете просто переместить один каталог.
Кристофер Карел
1
Поскольку копирование файлов не требует дополнительных затрат, это вполне приемлемое решение.
Тобу
2
Это работает, только если они находятся в одной файловой системе. Будет ли rsync с опцией удаления выполнять перемещение, если они находятся в одной файловой системе? (то есть, просто измените информацию каталога, но не перемещайте файл).
Рональд Поттол
1
rsync скопирует, а затем удалит, если пройдет через файловые системы.
karmawhore
5
Одно предостережение: сделать --link-destпуть абсолютным или относительным merged/; или это будет копировать.
Тобу
21

Странно, никто не заметил, что cpесть вариант -l:

-l, --link
       файлы жестких ссылок вместо копирования

Вы можете сделать что-то вроде

% mkdir merge
% cp -rl dir1 / * dir2 / * объединить
% rm -r dir *
% дерева слияния 
слияние
A── а
│ ├── file1.txt
│ ├── file2.txt
File ├── file5.txt
│ └── file6.txt
B── б
│ ├── file3.txt
File ├── file7.txt
File └── file8.txt
C── с
    File── file10.txt
    File── file4.txt
    File── file9.txt

13 каталогов, 0 файлов
Максимилиан
источник
Это не работает на разных жестких дисках ...
Алекс Лич
4
Правильнее будет сказать, что он не работает в файловых системах, поскольку файловые системы могут работать на нескольких жестких дисках. Кроме того, если оператору не нужно копировать файлы, это хорошая вещь, cp -lкоторая не работает в файловых системах.
lvella
2
Вы можете использовать cp -a(синоним cp -RPp), чтобы сохранить все атрибуты файлов и избегать следующих символических ссылок: здесь команда становится cp -al dir1/* dir2/* merge.
Трикасс
5

Для этого вы можете использовать переименование (также известное как prename из пакета perl). Помните, что имя не обязательно относится к команде, которую я описываю за пределами debian / ubuntu (хотя это единственный переносимый файл perl, если вам это нужно).

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

У вас также есть возможность использовать vidir (из moreutils) и редактировать пути к файлам из предпочитаемого вами текстового редактора.

Tobu
источник
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 разграничить его ввод с новой строкой и пропустить перевод. например, следующий код найдет и удалит все ваши торрент-файлы в текущем каталоге, даже те, которые содержат символы Юникода или некоторые другие дураки. 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. Просматривайте различия между двумя точками фиксации и принимайте осторожные решения о том, как я хочу объединить результаты.

Вы можете уточнить это с помощью ветвления и так далее, но это общая идея. И у вас меньше страха перед тем, как его заполнить, потому что у вас есть полный снимок каждого состояния.


источник