合并Linux中的2个目录树而不进行复制?


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”,则由于目录名称冲突而导致“文件存在”错误。

更新:您可以假定两个目录树之间没有重复的文件。


您确定两个文件夹之间没有文件名重复吗?如果有重复,您想怎么办?
Zoredache

如果实际上在单个目录中有数百万个文件,则出于性能原因,您应该考虑将文件分成多个单独的子目录-尽管这与所询问的实际问题无关。
DrStalker

Answers:


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

这将创建硬链接而不是移动它们,您可以验证它们是否正确移动,然后删除dir1/dir2/


9
的种类。它实际上并没有重复使用任何磁盘,它只是创建了另一个指向同一块磁盘的指针,并且实际上并未“复制”任何数据。(请参阅en.wikipedia.org/wiki/Hard_links)但是,每个文件确实必须执行一次该操作。但这基本上就是所有这些答案的全部内容,因为您不能只移动一个目录。
Christopher Karel 2010年

1
由于它没有复制文件的io开销,因此这是一个完全可以接受的解决方案。
东武

2
但是,这仅在它们位于同一文件系统上时才有效。如果rsync与delete选项位于同一文件系统上,是否可以移动?(也就是说,仅更改目录信息,而不移动文件)。
罗纳德·帕托

1
rsync将复制,如果遍历文件系统,则将其删除。
karmawhore 2010年

5
一个警告:将--link-dest路径设为绝对路径或相对于路径merged/;否则它将复制。
东武

21

奇怪的是没有人注意到cp有选择-l

-l,-link
       硬链接文件,而不是复制

你可以做类似的事情

%mkdir合并
%cp -rl dir1 / * dir2 / *合并
%rm -r目录*
%树合并 
合并
├──一个
│├──file1.txt
│├──file2.txt
│├──file5.txt
│└──file6.txt
├──b
│├──file3.txt
│├──file7.txt
│└──file8.txt
c──c
    ├──file10.txt
    ├──file4.txt
    └──file9.txt

13个目录,0个文件

这不适用于不同的硬盘驱动器...
Alex Leach 2012年

4
说它不能跨文件系统工作更正确,因为文件系统可以跨多个硬盘驱动器。另外,如果op希望避免复制文件,那么这是一件好事,cp -l不适用于整个文件系统。
lvella

2
您可能想要使用cp -a(的同义词cp -RPp)来保留文件的所有属性,并避免遵循以下符号链接:命令变为cp -al dir1/* dir2/* merge
tricasse 2012年

5

您可以为此使用重命名(又名,来自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

我喜欢rsyncprename解决方案,但是如果您真的想让mv做这项工作,

  • 发现-print0并知道-depth
  • 您的xargs知道-0
  • 你有printf

那么就有可能使用Bourne样式的shell脚本处理名称中可能带有随机空格的大量文件:

#!/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将其输入定界为换行符,然后跳过翻译。例如,以下内容将查找并删除当前目录下的所有torrent文件,甚至是具有Unicode字符或其他伪造信息的文件。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. 查看两个提交点之间的差异,并仔细决定如何合并结果。

您可以使用分支等方法进行精细处理,但这是一般的想法。而且您不必担心填充它,因为您具有每个状态的完整快照。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.