Bash 命令在更改目录树中“搜索并替换”文件

Bash 命令在更改目录树中“搜索并替换”文件

我已经在我的服务器上的中央存储库中构建了一个用于研究的文件集合。我不断地策划存储库,通过重命名和移动目录来更改目录的树结构。我不断更新和编辑存储库上文件的本地副本。这会造成这样的情况:我在存储库中有一个文件需要被客户端上的新文件替换,但目录树已更改。

有没有rsync我还没学过的选项?我发现 rsync 无法与更改目录树一起使用。我测试过rsync --existing

或者,有没有一些命令行-fu使用findmv?搜索网络和其他资源仅查找搜索和替换在文件中例子。

-- 更新 1 如果可以的话,我会给@meuh 和@Lqueryvg 一些精彩的答案。我看到 @css1971 添加到他的答案中,并为颠覆文件管理提供了更有力的案例。不幸的是,我没有时间了,奖励系统只是为我选择了最喜欢的答案。我发现所有的回复都提供了令人难以置信的信息。我要感谢大家。我期待着下周末的某个时间给他们更多深思熟虑的评论。

答案1

[从我的评论扩展到OP]。在存储库上创建一个目录flat,其中包含所有文件的副本,但位于一个平面列表中。副本将是硬链接,因此不要占用空间。在本地计算机上执行相同的操作。然后,您可以从本地平面目录 rsync 到远程平面目录。这将更新所有远程文件,因为如果您使用 rsync ,则会保留远程硬链接--inplace。平面目录可以在 rsync 之前由脚本创建,并在 rsync 之后删除。

这是一个可运行的概念验证测试脚本:

#!/bin/bash
dosync(){  # function to create flat trees and rsync them
    mkdir flat
    mkdir flatl
    find repo  -type f -exec ln {} flat \;
    find local -type f -exec ln {} flatl \;
    rsync -aHv --inplace flatl/ flat
    rm -fr flat flatl
}

# create repo and local dirs with same content. 3 empty files
cd /tmp || exit
mkdir repo
( cd repo; touch a b c )
mkdir local
rsync -a repo/ local

dosync
echo hi >local/a   # example change of local file a
dosync
mkdir repo/new     # example move of repo file b
mv repo/b repo/new/
echo hello >local/b  # change local file b
dosync

ls -lR repo local
# rm -fr flat flatl repo local

对于相反的方向,在 dosync 将本地修改传输到存储库后,您只需rm -fr local使用“rsync -a repo/ local”即可将完整的存储库复制到本地文件系统。相反,您可以使用类似的技术减少需要传输到存储库中的新文件的数量:

reversesync(){
    mkdir flat
    mkdir flatl
    find repo  -type f -exec ln {} flat \;
    find local -type f -exec ln {} flatl \;
    mv flat repo/flat
    mv flatl local/flat # not flatl!
    rsync -aHv --delete repo/ local
    rm -fr repo/flat local/flat
}

这会将扁平树分别移动到存储库和本地目录中,以便 rsync 可以看到硬链接文件并避免复制它们。 (显然,这次平面目录必须具有相同的名称)。


如果您只有一个已更改的已知文件,则可以find在存储库上使用它来获取其在树中的新位置并将该文件同步到那里。例如:

file=mychangedfile.abc
to=$(find repo -name "$file")
from=$(find local -name "$file")
rsync -av "$from" "$to"

这假设存储库已安装,否则您可以使用ssh repo find....如果您无法 ssh 到存储库,您可以使用 rsync 到虚拟本地目标来获取文件列表,并提取您想要的文件:

to=$(rsync -a --list-only repo dummy | awk '/\/'"$file"'$/{print $NF}')

答案2

我认为 rsync 是错误的工具,find 和 mv 也是如此。我的建议是使用软件配置管理系统。其中包括 Subversion、Git、Mercurial、Bazaar 等。所有这些都可以轻松处理树结构的变化。

在您描述的结构中,您的客户端系统上有树结构 A,而辅助位置(可能是本地的,也可能是远程的)上有存储库树结构 B。

如果您对两者都有更新,那么您现在必须应用竞争性更改以正确的顺序使您的两个存储库保持一致。如果它们不按顺序应用,您就会陷入现在所处的情况,其中一个结构的更改无法直接应用于另一个结构,因为结构已不存在。

就目前情况而言,rsync 没有任何选项可以让它自动知道必须应用哪些更改才能使两个存储库保持一致性。这不是它的设计目的。它当然可以使一个存储库看起来与另一个存储库完全相同,但它要求一次仅更改一侧。例如,交替更改 A、B、A。在任何时候,您都需要指定结构 A 或 B 之一作为主控并同步更改只朝一个方向一次。

我也不相信有一个简单的 commandline-fu 命令可以实现您正在寻找的结果,所以现在您进入了 shell 编程领域。

如果您仅更改了树结构B 且仅到文件内容对于 A 来说,查找已更改文件的文件名并获取这些文件在 B 中的新路径,然后修改 A 的树结构以进行匹配是一个相对简单的任务。这仅有的如果文件名是唯一的,则有效。

使 A 的结构与 B 保持一致的伪代码如下所示:

generate list of file names in A and their paths

For each of the names in A
    find that same name in B
    If the path of A is the same as B 
        continue to the next file
    if not then
        create the directory structure in A
        move the file to the new location.
    if the old path in A is now empty
        delete the directory.
        repeat 
            check if the parent directory is now empty, then delete it.
        until a non empty directory

一旦树结构同步,就可以将 A 直接复制到 B 中完全相同的路径。 rsync 的 --update 选项可用于在两个方向上用较新的文件覆盖较旧的文件。

一些示例 shell 代码,使用 find 作为文件名选择器将本地更改的文件复制到现有存储库中。

#!/bin/bash

set -xv

localRepo=/tmp/a
remoteRepo=/tmp/b

rm -rf $localRepo $remoteRepo

mkdir -p $localRepo/1/2/ $localRepo/1/3/
mkdir -p $remoteRepo/2/1/ $remoteRepo/3/1/

echo a12 > $localRepo/1/2/file
echo b21 > $remoteRepo/2/1/file

echo a13 > $localRepo/1/3/file1
echo b31 > $remoteRepo/3/1/file1

echo ex1
cat $localRepo/1/2/file $remoteRepo/2/1/file
echo ex2
cat $localRepo/1/3/file1 $remoteRepo/3/1/file1



localFileNameList=$(find $localRepo -type f -mtime -1 | xargs -L 1 basename)


for localFileName in $localFileNameList
do
    localFilePath=$(find $localRepo -name $localFileName | xargs dirname)
    backFile=$(find $remoteRepo -name $localFileName)
    repoDir=$(dirname $backFile)

    cp $localFilePath/$localFileName $repoDir

done

echo ex1
cat $localRepo/1/2/file $remoteRepo/2/1/file
echo ex2
cat $localRepo/1/3/file1 $remoteRepo/3/1/file1

例如,要将文件系统导入到 Subversion,作为更易于使用的 SCM 之一:

e.g.
mkdir /tmp/svn
svnadmin create /tmp/svn/reponame

cd /tmp/b
svn import -m "The initial import " file:///tmp/svn/reponame
Adding         2
Adding         2/1
Adding         2/1/file
Adding         3
Adding         3/1
Adding         3/1/file1

然后检查存储库并进行本地更改。

$ cd /tmp
$ svn checkout file:///tmp/svn/reponame 
A    reponame/2
A    reponame/2/1
A    reponame/2/1/file
A    reponame/3
A    reponame/3/1
A    reponame/3/1/file1
Checked out revision 1.
/tmp:

$ cd reponame/
/tmp/reponame:
$ ls -ltr
total 8
drwxrwxr-x 3 css1971 css1971 4096 Apr 11 12:04 3
drwxrwxr-x 3 css1971 css1971 4096 Apr 11 12:04 2
/tmp/reponame:
$ svn move 3 4
A         4
D         3
D         3/1
D         3/1/file1
/tmp/reponame:

将更改提交回存储库。

$ svn commit -m "renamed dir"
Deleting       3
Adding         4

Committed revision 2.

从此时起,使用 svn 工具作为正常工作流程的一部分来操作存储库。

有用的命令:

svn import
svn update
svn commit
svn del
svn cp
svn mv

命令参考: http://svnbook.red-bean.com/en/1.7/svn.ref.html

答案3

我认为这是一份工作齐奏!我已经很多年没有玩过它了,但我认为它完全可以满足你的要求......主页上写着:

Unison 是一个适用于 OSX、Unix 和 Windows 的文件同步工具。它允许将文件和目录集合的两个副本存储在不同的主机(或同一主机上的不同磁盘)上,分别进行修改,然后通过将每个副本中的更改传播到另一个副本来更新。

看看并随时通知我们! ;)

答案4

你绝对与不断变化的目录结构一起使用rsync,并且有一些您可能不知道的有趣选项,特别是-H保留硬链接的选项。

我将描述我自己的场景。这可能适合你,也可能不适合你,但我希望你至少找到它有趣的

设想:

您有一个大目录,目录树中包含许多文件,这些文件被复制rsync到另一个目录(可能在远程计算机或外部磁盘上)。

您想要重组目录,可能重命名文件和/或将它们移动到不同的子目录,但不更改文件本身的内容。

但是,下次运行时,rsync它会重新复制所有已移动或名称​​已更改的文件的数据,即使它们已经存在于目标上(尽管位于不同的位置)。以下是一种无需再次复制数据即可同步文件位置的方法,是快的

显然,首先在测试系统上尝试一下,然后当心与您的数据。

假设您的源数据位于 中/tmp/src/dir/,目标副本位于 中/tmp/dst/。请注意,这也适用于远程rsync目标。

0。

设置示例:

$ mkdir -p /tmp/src/dir; cd $_
$ fallocate -l 1000 a
$ fallocate -l 1000 b
$ fallocate -l 1000 c

$ tree /tmp/src
/tmp/src
`-- dir
    |-- a
    |-- b
    `-- c

1.

初始副本:

$ mkdir /tmp/dst; rsync -havHP /tmp/src/dir /tmp/dst

至此,/tmp/src/dir/其中的所有文件和目录都已复制到/tmp/dst/.

$ tree /tmp/dst
/tmp/dst
`-- dir
    |-- a
    |-- b
    `-- c

2.

在源上制作目录结构的硬链接副本。注意:即使有很多文件和目录,这也是如此很快,因为它只是复制元数据:

$ cd /tmp/src/; mv dir dir.old
$ cp -rlp dir.old dir

# -l = link not copy
# -p = preserve permissions etc

3.

在 中进行大量更改/tmp/src/dir,包括移动和重命名文件。在此示例中,我将把所有文件移动到一个新的子目录中。

$ cd /tmp/src/dir; mkdir sub
$ mv a b c sub
$ tree /tmp/src/dir
/tmp/src/dir
`-- sub
    |-- a
    |-- b
    `-- c

4.

交换新旧目录结构后,rsync目标旁边的是快的,因为它只复制目标上的硬链接,除非文件内容发生更改,否则不会复制数据。

$ cd /tmp/src
$ mv dir dir.new; mv dir.old dir
$ rsync -havHP --delete-after --no-inc-recursive \
/tmp/src/dir /tmp/src/dir.new /tmp/dst

请注意,rsync 的输出显示它不会再次传输文件内容:

building file list ... 
9 files to consider
dir.new/
dir.new/sub/
dir.new/sub/a => dir/a
dir.new/sub/b => dir/b
dir.new/sub/c => dir/c

sent 165 bytes  received 45 bytes  420.00 bytes/sec
total size is 6.00K  speedup is 28.57

5.

最后,通过在源和目标上将原始内容替换dir为新内容来进行整理:dir.new

$ cd /tmp/src
$ rm -rf dir
$ mv dir.new dir

$ cd /tmp/dst
$ rm -rf dir
$ mv dir.new dir

上面的解决方案有点繁琐,您应该小心其他人在目录交换期间访问数据,但这无疑是一个有趣的功能,rsync在某些情况下可以节省大量时间。

相关内容