如何强制使用“cp”命令覆盖目录而不是在其中创建另一个目录?

187

我想写一个Bash脚本,用于覆盖现有的目录。我有一个名为foo/的目录,并且我正在尝试用它来覆盖bar/。但是当我这样做时:

cp -Rf foo/ bar/

创建了一个新的bar/foo/目录,我不想要它。在foo/中有两个文件;ab。在bar/中也有相同名称的文件。我希望foo/afoo/b替换bar/abar/b

10个回答

183
你可以使用cp命令的-T选项来完成此操作。
详见cp命令的Man手册。
-T, --no-target-directory
    treat DEST as a normal file

根据您的示例,以下是文件结构。

$ tree test
test
|-- bar
|   |-- a
|   `-- b
`-- foo
    |-- a
    `-- b
2 directories, 4 files

当您使用-v(详细信息)选项时,您可以看到明显的差异。
当您只使用-R选项时。

$ cp -Rv foo/ bar/
`foo/' -> `bar/foo'
`foo/b' -> `bar/foo/b'
`foo/a' -> `bar/foo/a'
 $ tree
 |-- bar
 |   |-- a
 |   |-- b
 |   `-- foo
 |       |-- a
 |       `-- b
 `-- foo
     |-- a
     `-- b
3 directories, 6 files

当您使用选项-T时,它会覆盖内容,将目标视为普通文件而非目录。
$ cp -TRv foo/ bar/
`foo/b' -> `bar/b'
`foo/a' -> `bar/a'

$ tree
|-- bar
|   |-- a
|   `-- b
`-- foo
    |-- a
    `-- b
2 directories, 4 files

这应该解决你的问题。

29
以防有人被绊倒,需要说明一下,此方法不能在OSX的cp命令中使用。参考链接:https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/cp.1.html - dnfehren
18
虽然上面给出的例子掩盖了问题,但并不清楚这个答案是否符合OP的要求。使用-T选项时,已经存在于目标(bar/)中但在源(foo/)中不存在的文件将被保留在原地,因此大多数人不会认为这是完全覆盖目录。也就是说,如果bar/baz已经存在,执行操作后它仍将存在... - robo
5
这个答案回答了问题,但没有解决目标文件夹已经存在且您想删除其内容但源文件夹不存在的情况。这不是从一个地方复制文件到另一个地方的预期行为。它只会覆盖源中也存在于目标中的东西,不会改变目标中不在源中的任何东西。您可以在命令前添加一个清理目标文件夹的命令:rm -rf bar/* && cp -TRv foo/ bar/ - theferrit32
3
我不是心灵读者...我没有看到更进一步的澄清,了解OP想要什么,但这绝对是我(我自己)在寻找的答案。 - Assimilater
3
以防万一有人因为最受欢迎的评论中的链接无法使用而感到困惑,这里是链接:https://web.archive.org/web/20170909193852/https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/cp.1.html - Sulphur
显示剩余2条评论

110

如果您想确保bar/foo/完全相同,请使用rsync

rsync -a --delete foo/ bar/

如果只有一些内容发生了变化,使用这种方法比删除并重新复制整个目录要快得多。

  • -a 是“归档模式”,它将foo/中的文件忠实地复制到bar/
  • --delete也会从bar/中删除不在foo/中的额外文件,确保bar/最终与foo/相同
  • 如果想看到它在做什么,可以添加-vh来查看详细和易读的信息
  • 注意: 在foo后面加上斜杠是必需的,否则rsync会将foo/复制到bar/foo/而不是覆盖bar/本身。
    • (在rsync中目录后面的斜杠很困惑;如果你感兴趣,这里有更多信息。它们告诉rsync引用目录的内容,而不是目录本身。所以为了从foo/的内容覆盖到bar/的内容,我们在两个目录名后都使用了一个斜杠。这是令人困惑的,因为如果两个目录名后都不加斜杠,它将不会按预期工作;rsync总是偷偷地将目标路径解释为带有斜杠,即使它遵循源路径上斜杠的缺失。因此,如果我们想要将foo/的内容复制到bar/中,而不是将目录foo/本身放置到bar/中作为bar/foo,我们需要在源路径上加上斜杠,以使其与自动添加斜杠的目标路径匹配)。

rsync非常强大和有用,如果你感兴趣,可以找找它还能做什么(例如通过ssh进行复制)。


3
我更喜欢这个答案而不是使用 cp -T 选项,因为它也适用于macOS系统。 - tongueroo
@Tom 最终结果是否与下面给出的答案相同:https://dev59.com/-mAg5IYBdhLWcg3wWpz-#23698290 - Porcupine

82

分两步来做。

rm -r bar/
cp -r foo/ bar/

16
目前只有这个例子可以确保 bar 的内容与 foo 完全相同,而不是由 foo 中的项加上已经存在于 bar 中的其他项组成。下面 @Saurabh Meshram 得到高赞的答案就存在这个问题。 - robo
1
在使用此功能时要特别小心,因为它将删除bar中的所有文件,包括隐藏的文件。 - Elia Grady
1
mac osx:cp -r foo/ !$``` - Michael Dimmitt
2
这并不总是可行的...或者说并不是期望的...想象一下如果 foobar 是大型目录...也许在 bar 中有一些文件不在 foo 中,你不想删除它们。我认为这不应该被视为一个现实的答案。 - Assimilater
2
@Porcupine rsync - Jonathan Wheeler
显示剩余5条评论

31

使用这个cp命令:

cp -Rf foo/* bar/

13
这并不会删除在bar中存在但不在foo中的文件。 - Ara
11
不清楚你的意思。为什么cp命令会从源文件中删除文件?请问能否解释一下? - anubhava
我的理解是,当你用另一个目录“覆盖现有目录”时,最终被覆盖的目录应该是另一个目录的副本。 也就是说,最终bar应该是foo的副本,就像@jonathan-wheeler的答案一样,但如果你有一个文件bar/c而没有foo/c,那么bar/c不会被删除。 顺便说一句,我刚刚注意到Saurabh Meshram的答案也不是这种情况。 - Ara
也许我们对覆盖的含义存在分歧,对我来说它基本上是替换,而你可能指的是合并?很难确定 OP 究竟想要什么,因为她只提到了两个文件,它们都在 foo 和 bar 中。 - Ara
2
这个语法也会忽略隐藏的点文件。大量的数据也可能会使glob失效。 - xpusostomos
可以使用 shopt 进行控制。 - anubhava

18
以下命令可以确保复制中包括点文件(隐藏文件):

以下命令可以确保复制中包括点文件(隐藏文件):

$ cp -Rf foo/. bar

1
这是真的吗?看起来很不寻常。 - Mateng
3
我刚刚测试了一下,是的,它是真的。 - Matmarbon
1
“.” 表示目录中的所有文件,因此自然会包括隐藏文件。 - Jaspreet Singh
“.”(点)只是表示当前目录,对吧? - JohnyTex
-a-p一起使用,这也确保目标目录接收源目录的元数据(所有者、权限)--非常好! - schlimmchen
显示剩余2条评论

7

和@Jonathan Wheeler非常相似:

如果你不想记住,但不想重新编写bar

rm -r bar/
cp -r foo/ !$

!$ 显示你上一个命令的最后一个参数。


6
有关 !$ 的提示真的很棒! - Lucas

3
这应该解决您的问题。
\cp -rf foo/* bar/

相同的答案在这里 https://dev59.com/-mAg5IYBdhLWcg3wWpz-#23698218 - Nick Roz

0
如果您使用的是Git Bash for Windows

robocopy 路径/到/源路径 路径/到/目标路径 -MIR

-MIR 表示镜像,它将生成两个相同的文件夹。现有文件将被跳过,额外的文件将被删除。


-1
您定义的操作是“合并”,不能使用cp进行操作。但是,如果您不需要合并,并且可以删除文件夹bar,那么您可以简单地使用rm -rf bar删除该文件夹,然后使用mv foo bar重命名它。这不会花费任何时间,因为这两个操作都是通过文件指针完成的,而不是文件内容。

-6

尝试使用这个命令:

cp -Rf foo/* bar/

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接