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.


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


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

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.


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

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

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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.