Linuxで2つのディレクトリツリーをコピーせずにマージしますか?


35

同様のレイアウトの2つのディレクトリツリーがあります。

.
 |-- 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」を使用すると、ディレクトリ名が競合するため「ファイルが存在します」エラーが表示されます。

更新:2つのディレクトリツリーの間に重複するファイルがないと仮定できます。


2つのフォルダー間でファイル名の重複はありませんか?重複がある場合はどうしますか?
ゾレダチェ

単一のディレクトリに文字通り何百万ものファイルがある場合、パフォーマンス上の理由から、ファイルを別々のサブディレクトリに分割することを検討する必要があります-これは、実際の質問とは無関係です。
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
ファイルをコピーするioオーバーヘッドがないため、これは完全に受け入れられるソリューションです。
東武

2
ただし、同じファイルシステム上にある場合にのみ機能します。削除オプションを指定したrsyncは、同じファイルシステム上にある場合に移動しますか?(つまり、ディレクトリ情報を変更するだけで、ファイルは移動しません)。
ロナルドポトル

1
rsyncはファイルシステムを横断する場合、コピーしてから削除します。
カルマホーレ

5
1つの注意:--link-destパスを絶対パスまたは相対パスにしmerged/ます。またはコピーします。
東武

21

それcpがオプションを持っていることに誰も言及していないのは奇妙-lです:

-l、-link
       コピーする代わりにハードリンクファイル

次のようなことができます

%mkdir merge
%cp -rl dir1 / * dir2 / * merge
%rm -r dir *
%ツリーマージ 
マージ
├──a
│├──file1.txt
│├──file2.txt
│├──file5.txt
│└──file6.txt
├──b
│├──file3.txt
│├──file7.txt
│└──file8.txt
└──c
    ├──file10.txt
    ├──file4.txt
    └──file9.txt

13ディレクトリ、0ファイル

これは...別のハードドライブにまたがって動作しません
アレックス・リーチに

4
ファイルシステムは複数のハードドライブにまたがることがあるため、ファイルシステム間では機能しないと言う方が正しいです。また、opがファイルのコピーを避けたい場合は、cp -lファイルシステム間で機能しないことをお勧めします。
lvella

2
ファイルのすべての属性を保持し、シンボリックリンクをたどらないようにするためにcp -a(と同義語cp -RPp)を使用することができますcp -al dir1/* dir2/* merge。ここでコマンドはになります。
トリカス

5

そのために、名前変更(perlパッケージのprename)を使用できます。名前は必ずしもdebian / ubuntu以外で説明するコマンドを参照しているわけではないことに注意してください(ただし、必要な場合は単一のポータブルperlファイルです)。

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

vidir(moreutilsから)を使用し、好みのテキストエディターからファイルパスを編集するオプションもあります。


3

私はrsyncprenameソリューションが好きですが、本当にmvに仕事をさせたいなら

  • あなたの発見は知って-print0おり-depth
  • あなたのxargsは知っています-0
  • あなたは持っているのprintfを

名前にランダムな空白が含まれる可能性のある多数のファイルを、すべて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

入力を改行に区切り、翻訳をスキップするように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
OPは数百万のファイルを指定しましたが、これはこの構造を破壊する可能性があります。また、それは適切など。スペース、改行、を含むファイル名を処理しません
クリス・ヨンセン

0

開発のさまざまな段階で、ソースコードツリーに対してこれを数回行う必要がありました。私の解決策は、Gitを次のように使用することでした。

  1. gitリポジトリを作成し、dir1からすべてのファイルを追加します。
  2. コミット
  3. すべてのファイルを削除し、dir2からファイルをコピーします
  4. コミット
  5. 2つのコミットポイントの違いを確認し、結果をどのようにマージするかについて慎重に決定します。

分岐などで細かく処理できますが、これは一般的な考え方です。また、各状態の完全なスナップショットがあるため、詰め込むことに対する不安が少なくなります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.