Объединить 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
Поскольку копирование файлов не требует дополнительных затрат, это вполне приемлемое решение.
Тобу

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) и редактировать пути к файлам из предпочитаемого вами текстового редактора.


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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.