Mesclar 2 árvores de diretório no Linux sem copiar?

35

Eu tenho duas árvores de diretório com layouts semelhantes, ou seja,

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

Gostaria de mesclar as árvores de diretório dir1 e dir2 para criar:

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

Sei que posso fazer isso usando o comando "cp", mas quero mover os arquivos em vez de copiar, porque os diretórios reais que quero mesclar são muito grandes e contêm muitos arquivos (milhões). Se eu usar "mv", recebo o erro "O arquivo existe" devido a nomes de diretório conflitantes.

ATUALIZAÇÃO: Você pode assumir que não há arquivos duplicados entre as duas árvores de diretório.

bajafresh4life
fonte
Tem certeza de que não há duplicação de nomes de arquivos entre as duas pastas? o que você quer que aconteça se houver duplicatas?
Zoredache
Se você literalmente possui milhões de arquivos em um único diretório, deve dividir os arquivos em subdiretórios separados por motivos de desempenho - embora isso seja irrelevante para a pergunta real.
DrStalker

Respostas:

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

Isso criaria hardlinks em vez de movê-los. Você pode verificar se eles foram movidos corretamente e, em seguida, remover dir1/e dir2/.

karmawhore
fonte
9
Mais ou menos. Na verdade, ele não duplica o uso de disco, simplesmente cria outro ponteiro para o mesmo pedaço de disco e, na verdade, não 'copia' nenhum dado. (Consulte en.wikipedia.org/wiki/Hard_links ) No entanto, é necessário executar essa operação uma vez por arquivo. Mas é essencialmente o que todas essas respostas acabam fazendo, já que você não pode simplesmente mover um único diretório.
Christopher Karel
1
Como ele não possui a sobrecarga de copiar arquivos, essa é uma solução perfeitamente aceitável.
Tobu
2
Isso só funciona se eles estiverem no mesmo sistema de arquivos. O rsync com a opção delete faria uma mudança se eles estivessem no mesmo sistema de arquivos? (ou seja, basta alterar as informações do diretório, mas não mover o arquivo).
Ronald Pottol
1
O rsync copiará e excluirá se ele percorrer os sistemas de arquivos.
karmawhore
5
Uma ressalva: torne o --link-destcaminho absoluto ou relativo a merged/; ou ele irá copiar.
Tobu
21

É estranho que ninguém tenha notado que cptem opção -l:

-l, --link
       arquivos de link físico em vez de copiar

Você pode fazer algo como

% mkdir mesclar
% cp -rl dir1 / * dir2 / * mesclagem
% rm -r dir *
% mesclagem de árvore 
fundir
├── a
1 ├── file1.txt
2 ├── file2.txt
5 ├── file5.txt
6 └── file6.txt
├── b
3 ├── file3.txt
7 ├── file7.txt
8 └── file8.txt
└── c
    10── file10.txt
    ├── file4.txt
    └── file9.txt

13 diretórios, 0 arquivos
Maximilian
fonte
Isso não funciona em diferentes discos rígidos ...
Alex Leach
4
É mais correto dizer que ele não funciona em sistemas de arquivos, porque os sistemas de arquivos podem se estender por vários discos rígidos. Além disso, se o que o op quer é evitar a cópia dos arquivos, é bom que cp -lnão funcione nos sistemas de arquivos.
Lvella 22/05/12
2
Você pode usar cp -a(sinônimo de cp -RPp) para manter todos os atributos dos arquivos e evitar os seguintes links simbólicos: aqui o comando se torna cp -al dir1/* dir2/* merge.
Tricasse
5

Você pode usar renomear (também conhecido como nome do pacote perl) para isso. Cuidado que o nome não se refere necessariamente ao comando que descrevo fora do debian / ubuntu (embora seja um único arquivo perl portátil, se você precisar).

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

Você também tem a opção de usar o vidir (do moreutils) e editar os caminhos do arquivo no seu editor de texto preferido.

Tobu
fonte
3

Eu gosto das soluções rsync e prename , mas se você realmente quer que o mv faça o trabalho e

  • seu achado sabe -print0e -depth,
  • seu xargs sabe -0,
  • você tem printf ,

é possível manipular um grande número de arquivos que podem ter espaços em branco aleatórios em seus nomes, todos com um script de shell no estilo Bourne:

#!/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
fonte
Você pode dizer ao xargs para delimitar sua entrada para nova linha e pular a tradução. por exemplo, o seguinte encontraria e excluiria todos os seus arquivos torrent no diretório atual, mesmo aqueles com caracteres unicode ou algum outro tipo de tolice. find . -name '*.torrent' | xargs -d '\n' rm
PRS
2

Força bruta 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

teste faz isso

# 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
fonte
2
O OP especificou milhões de arquivos, o que provavelmente interromperá essa construção. Além disso, ele não vai lidar correctamente com nomes de arquivos com espaços, novas linhas, etc ..
Chris Johnsen
0

Eu tive que fazer isso várias vezes para árvores de código fonte em diferentes estágios de desenvolvimento. Minha solução foi usar o Git da seguinte maneira:

  1. Crie um repositório git e adicione todos os arquivos do dir1.
  2. Confirmar
  3. Remova todos os arquivos e copie os arquivos do dir2
  4. Confirmar
  5. Veja as diferenças entre os dois pontos de confirmação e tome decisões cuidadosas sobre como eu quero mesclar os resultados.

Você pode refiná-lo com ramificações e assim por diante, mas essa é a idéia geral. E você tem menos medo de empacotá-lo porque possui um instantâneo completo de cada estado.


fonte