带有硬链接的cp的行为感到惊讶


20

我非常了解硬链接的概念,并且已经阅读了一些基本工具的手册页,例如cp---甚至是最新的POSIX规范---。我仍然惊讶地观察到以下行为:

$ echo john > john
$ cp -l john paul
$ echo george > george

在这一点上john,它们paul将具有相同的inode(和内容),并且george在两个方面都将有所不同。现在我们做:

$ cp george paul

在这一点上,我期望georgepaul拥有不同的inode编号,但是内容相同-实现了这一期望-但我期望paul现在拥有与in相同的inode编号john,并且john仍然具有内容john。这就是我感到惊讶的地方。事实证明,将文件复制到目标路径paul还导致在共享inode的所有其他目标路径上安装相同文件(相同inode)的结果paul。我当时想cp创建一个新文件,然后将其移动到旧文件所占据的位置paul。相反,它似乎要做的是打开现有文件paul,将其截断并写入george的内容添加到该现有文件中。因此,具有相同inode的任何“其他”文件都将同时更新“其”内容。

好的,这是系统性的行为,现在我知道可以期待它了,因此我可以弄清楚如何解决它,或者适当地利用它。让我感到困惑的是应该在哪里看到该行为记录在案?如果我没有看过文档中的某处,我会感到惊讶。但是显然我错过了,现在找不到讨论此行为的消息来源。

Answers:


4

首先,为什么要这样做?原因是历史悠久的:这就是在Unix First Edition中完成的方式

文件成对存放;打开第一个以读取,创建第二个模式17。然后将第一个复制到第二个模式。

“已创建”是指creat系统调用(众所周知是缺少e的系统调用),如果存在,它将按给定名称截断现有文件。

这里的源代码cp在Unix的第二版(我无法找到第一版的源代码)。您可以看到对open源文件和creat第二个文件的调用。并且,作为第二版的改进,如果第二个文件是现有目录,则cp在该目录中创建一个文件。

但是,您可能会问,当时为什么要这样做呢?关于“为什么Unix最初是这样做的”的答案几乎总是简单的。cp打开其源以进行读取并创建其目的地-创建文件的系统调用会通过打开该文件进行写入操作来覆盖现有文件,因为这允许调用者以给定名称强加文件的内容,无论该文件是否已存在或不。

现在,关于它的记录位置:在FreeBSD手册页中

对于每个已存在的目标文件,如果权限允许,其内容将被覆盖。除非指定了-p选项,否则其模式,用户ID和组ID不变。

这种措辞至少可以追溯到1990年(当时BSD为4.3BSD)。Solaris 10上有类似的措辞:

如果target_file存在,则cp会覆盖其内容,但是与之关联的模式(和ACL,如果适用),所有者和组不变。

HP-UX 10手册甚至阐明了您的情况:

如果new_file是具有其他链接的现有文件的链接,则覆盖现有文件并保留所有链接。

POSIX用标准语言表示。从Single UNIX v2引用:

如果存在dest_file,则采取以下步骤:(…)将通过执行等效于XSH规范open()函数的操作来获得dest_file的文件描述符,该函数使用dest_file作为path参数以及O_WRONLY和O_TRUNC的按位包含在内作为oflag参数。

我进一步引用的手册页和规范指定如果-f传递了选项并且打开/创建目标文件的尝试失败(通常是由于没有写文件权限),则cp尝试删除目标并再次创建文件。这将破坏您方案中的硬链接。

您可能想根据GNU coreutils手册报告一个文档错误,因为它没有文档记录此行为。甚至对的描述(--preserve=links在您的方案中会导致paul链接被删除并创建新文件)也无法弄清楚没有会发生什么--preserve=links。这种-f类型的描述暗含了没有它会发生的情况,但没有说明(“当不使用此选项进行复制并且无法打开现有目标文件进行写入时,复制将失败。但是,使用--force,...”)。


为什么说“因为允许调用者获得文件名的所有权,无论文件是否已存在”?Cp不拥有现有文件的所有权。
jrw32982

@ jrw32982我的意思是确定文件中包含的内容的所有权,而不是文件元数据的所有权。我已经改写了那句话。
吉尔斯2015年

20

cp记录如果目标文件已存在,它将覆盖目标文件。没错,它没有详细说明“覆盖”的含义,但肯定是“覆盖”,而不是“替换”。如果您想成为一个学究的人,则可以辩称“覆盖”正是这样cp做的意思,而您期望的行为将被适当地称为“替换”。

还应注意,如果cp要“替换”先前存在的目标文件,则可能比“覆盖”更合理或令人惊讶。例如:

  • 如果cp先删除旧文件然后创建一个新文件,则将有一段时间没有该文件,这是令人惊讶的。
  • 如果cp首先创建一个临时文件然后将其移到适当的位置,则它可能应该对此进行记录,因为这样的事实有时会发现带有奇怪名称的此类临时文件……但事实并非如此。
  • 如果cp由于权限原因无法在与旧文件相同的目录中创建新文件,那么这将是不幸的(特别是如果它已经删除了旧文件)。
  • 如果该文件不属于运行cp该用户的用户,而该用户不属于该运行的用户cproot那么将不可能使新文件的所有者和权限与新文件的所有者和权限匹配。
  • 如果文件具有未知的特殊特殊属性cp,则这些特殊属性将在副本中丢失。如今,的实现cp应该可靠地理解诸如扩展属性之类的东西,但并非总是如此。还有其他东西,例如MacOS资源派生,或者对于远程文件系统,基本上任何东西。

总结一下:现在您知道cp真正在做什么。您再也不会对此感到惊讶!老实说,我想很多年前我也可能发生过同样的事情。


必须检查POSIX参考,但是实际上BSD(至少是OSX)和Gnu版本上的man页面对于“覆盖”不是那么明确。该词仅用于对选项和的注释。Gnu联机帮助页特别没有信息,BSD / Mac联机帮助页至少开始说cpcp-i-nCopy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.In the first synopsis form, the cp utility copies the contents of the source_file to the target_file.
dubiousjim 2015年

Gnu coreutils信息页面开始:‘cp’ copies files (or, optionally, directories). The copy is completely independent of the original.
dubiousjim 2015年

2
我看到POSIX 2008标准确实指定了观察到的行为。我将添加一个答案。
dubiousjim

16

我看到POSIX 2013标准确实指定了观察到的行为。它说:

  1. 如果source_file是常规文件类型,则应采取以下步骤:

    一种。...如果存在dest_file,则应采取以下步骤:

    一世。如果该-i选项有效,则cp实用程序应向标准错误写入提示,并从标准输入中读取一行。如果响应不是肯定的,cp则对source_file不再执行任何操作, 然后继续处理剩余的文件。

    ii。对于A文件描述符dest_file应由相当于执行动作来获得open()在POSIX.1-2008的系统接口体积定义函数使用称为dest_file作为路径参数,按位包容ORO_WRONLYO_TRUNC作为oflag中的参数。

    iii。如果获取文件描述符的尝试失败并且该-f选项生效,cp则应尝试执行等效于unlink()使用dest_file作为path参数调用的POSIX.1-2008系统接口卷中定义的功能的操作来删除文件。如果此尝试成功,cp则应继续执行步骤3b。

    ...

    d。source_file的内容应写入文件描述符。任何写入错误均应导致cp将诊断消息写入标准错误,然后继续执行步骤3e。

    e。文件描述符应关闭。


1
有趣。像您一样,我假设cp会给与类似的结果mv,并破坏目标所在的任何硬链接。但是,现在考虑到这一点,这意味着它必须专门unlink(2)指定目标(cp -f),或者创建一个名称不同的临时对象,然后再创建rename(2)它。直接实现是打开文件进行覆盖,这是POSIX所要求的。相当于cat src > dest
Peter Cordes

2

如果您可以说:“将文件复制到目标路径paul 还会将同一文件(相同的inode)复制到共享paulinode的所有其他目标路径。”很抱歉,您不了解硬链接很好。如果我给麦卡特尼爵士一个苹果,我就给保罗一个苹果,也给约翰·列侬的作曲伙伴一个苹果。但是我没有给三个苹果。我给一个具有多个名称/标题/描述符的人一个苹果。

同样,当你复制georgepaul,你是不是将其复制到john。而是将george数据复制到paul目录条目指向其inode的文件中。

循序渐进:   执行时

echo john > john

您已经创建了一个新文件(假设john该目录中没有命名的文件)。或者,更严格地说,这是假定该目录中没有名称的目录条目john(因为严格来说,目录中没有文件;仅目录条目,它指向inode)。做完之后

cp -l john paul

要么

ln john paul

您尚未创建新文件;而是,您为现有文件指定了新名称。现在,您有一个文件,有两个名称:johnpaul。当你说

cp george paul

您正在覆盖该文件。它有两个名称这一事实是无关紧要的。它可能有42个名称,可能在您甚至无法访问的地方,并且该命令不会将george\n数据复制到所有这些名称(路径);它只是将数据复制到具有多个名称的一个文件中。


1
谢谢。是的,我意识到我在编写时所写的东西需要用引号引起来:john并且paul以同一个文件的两个路径名开头。但这是我想表达自己的最简单方法。我不认为一个硬链接,正确理解的单纯概念,规定无论是这两种行为对cp(不含-l)。
dubiousjim

但是谢谢你的推动;我试图澄清措辞。
dubiousjim 2015年
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.