为什么将cp设计为以静默方式覆盖现有文件?[关闭]


30

cp使用以下命令进行了测试:

$ ls
first.html   second.html  third.html

$ cat first.html
first

$ cat second.html
second

$ cat third.html
third

然后我复制first.htmlsecond.html

$ cp first.html second.html

$ cat second.html
first

该文件将second.html被静默覆盖,没有任何错误。但是,如果我在桌面GUI中通过拖放具有相同名称的文件来执行此操作,则该文件将first1.html自动添加后缀。这样可以避免意外覆盖现有文件。

为什么不cp遵循这种模式,而不是默默地覆盖文件?


10
我想象只有coreutils设计师可以真正回答这个问题,但这只是目前的工作方式。通常,构建应用程序时会假设用户确实了解他们在做什么,并尽量减少额外的提示。如果要更改行为,请将别名“ cp”命名为“ cp -i”或“ cp -n”。
kevlinux

8
@kevlinux coreutils开发人员仅在实现POSIX标准。
库沙兰丹

17
因为早在设计之初,人们就希望他们的工作尽可能简洁(因此cp不复制),知道他们做了什么,当他们犯错时,他们并没有试图怪罪工具。那时,计算机是完全不同的人。就像问为什么心脏外科医师的手术刀也可以切入手中一样。
PlasmaHH

4
Unix是由计算机专家设计的,并且是为计算机专家设计的,假定用户知道他在做什么。如果可能,操作系统将完全按照用户的指示进行操作-无需握住用户的手,也无需进行无休止的确认。如果某个操作覆盖了某些内容,则认为这正是用户想要的。还请记住,这是在1970年代早期-MS DOS之前的Windows,Windows和家用计算机-引导并握住用户的手的每一步,这一点尚未普及。同样,以电传打字机作为终端,要求确认总是太麻烦了。
巴德·科珀罗德

10
不要别名cpcp -i或类似的,因为你习惯了在一个安全网,使系统中它不可用(大部分)是更危险。cp -i如果那是您想要的,最好教自己进行例行等。
Reid

Answers:


52

的默认覆盖行为cp在POSIX中指定。

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

    3.a. 如果dest_file存在并且是上一步编写的,则该行为未指定。否则,如果存在dest_file,则应采取以下步骤:

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

    3.a.ii. dest_file的文件描述符应通过执行与在POSIX.1-2017的System Interfaces卷中定义的open()函数等效的操作来获得,该操作使用dest_file作为路径参数,并使用O_WRONLY和O_TRUNC的按位“或”运算来获得。 oflag参数。

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

编写POSIX规范时,已经存在大量脚本,并且内置了默认覆盖行为的假设。这些脚本中有许多被设计为无需直接用户在场即可运行,例如作为cron作业或其他后台任务。改变行为会破坏他们。复习并修改它们,以添加一个选项,在任何需要的地方强制覆盖,可能被认为是一项具有最小收益的巨大任务。

同样,Unix命令行始终旨在让有经验的用户有效地工作,即使以初学者的辛苦学习为代价。当用户输入命令时,计算机将期望用户确实是该命令,而无需进行任何猜测。用户有责任小心可能具有破坏性的命令。

与最初的Unix相比,最初的Unix开发时,该系统的内存和大容量存储如此之少,覆盖了警告和提示的现代计算机可能被视为浪费和不必要的奢侈品。

在编写POSIX标准时,已经确立了先例,并且该标准的作者充分意识到了不破坏向后兼容性的优点。

此外,正如其他人所描述的那样,任何用户都可以通过使用Shell别名甚至通过构建替换cp命令并修改其功能$PATH以在标准系统命令之前找到替换项来自己添加/启用这些功能,并在以下情况下获得安全网:想要的。

但是,如果这样做,您会发现自己正在为自己造成危险。如果该cp命令在交互使用时表现为一种方式,而在脚本中调用时表现为另一种方式,则您可能不记得存在这种差异。在另一个系统上,您可能会变得粗心,因为您已经习惯了自己系统上的警告和提示。

如果脚本中的行为仍然符合POSIX标准,则您可能会习惯于交互式使用提示,然后编写一个进行大量复制的脚本-然后发现您又无意中覆盖了某些内容。

如果您也在脚本中强制执行提示,那么在没有用户的环境中运行该命令会如何操作,例如后台进程或cron作业?脚本会挂起,中止还是覆盖?

挂起或中止意味着原本应该自动完成的任务将不会完成。有时,不进行覆盖也可能会单独导致问题:例如,它可能导致旧数据被另一个系统处理两次,而不是被最新数据替换。

命令行的强大功能很大一部分来自于这样的事实,即一旦您知道如何在命令行上执行某项操作,您就会隐式地也知道如何通过脚本使它自动发生。但是,只有当您在脚本上下文中调用交互式地使用的命令也完全相同时,这才是正确的。交互使用和脚本使用之间在行为上的任何显着差异都将造成某种认知失调,这会困扰高级用户。


54
“为什么它会这样工作?” “因为该标准是这样说的。” “为什么标准这么说?” “因为它已经起作用了,所以喜欢它。”
Baptiste Candellier

16
最后一段是真正的原因。确认对话框和“ 您真的要这样做吗? ”提示是
w弱的

@BaptisteCandellier-同意。就像最终的原因就在眼前,但诱人的是,这个答案是遥不可及的。
TED

2
最后一段是为什么rm -rf如此有效的原因,即使您实际上并不是要在主目录中运行它也是如此……
Max Vernon

2
@TED有趣的是每当这些短暂的讨论再次抬起头来时没人会提到unlink(2)syscall如何也“失败”问“妈妈,我可以吗?”以进行确认。:)
tchrist

20

cp来自Unix的开始。在Posix标准制定之前就已经存在了。的确:Posix刚刚正式确定了cp这方面的现有行为。

我们谈论的是时代(1970-01-01),当时男人是真正的男人,女人是真正的女人,但毛茸茸的小动物……(我离题)。在那些日子里,添加额外的代码使程序更大。那是一个问题,因为运行Unix的第一台计算机是PDP-7(可升级到144KB RAM!)。因此,事情体积小,效率高,没有安全功能。

因此,在那些日子里,您必须知道自己在做什么,因为计算机无法阻止您以后做任何后悔的事情。

(Zevar有一本精美的漫画;搜索“ zevar cerveaux assistante par ordinateur”来查找计算机的演变。或者尝试http://perinet.blogspirit.com/archive/2012/02/12/zevar-et- cointe.html(只要存在)

对于那些真正感兴趣的人(我在评论中看到了一些猜测):第cp一个Unix上的原始版本大约有两页汇编代码(C后来出现)。相关部分是:

sys open; name1: 0; 0   " Open the input file
spa
  jmp error         " File open error
lac o17         " Why load 15 (017) into AC?
sys creat; name2: 0     " Create the output file
spa
  jmp error         " File create error

(所以很难sys creat

而且,虽然我们在使用它:使用了Unix的第2版(代码段)

mode = buf[2] & 037;
if((fnew = creat(argv[2],mode)) < 0){
    stat(argv[2], buf);

creat如果没有测试或保障措施,这也是很难的。请注意,V2 Unix的C代码cp少于55行!


5
几乎正确,当然是“ 小毛茸茸 ”(来自半人马座的生物)而不是“ 小毛茸茸 ”!
TripeHound18年

1
@TED:这是完全可能的早期版本,cp只是open编与目标O_CREAT | O_TRUNC,并进行了read/ write循环; 当然,对于现代人cp来说,有太多的旋钮,它基本上必须stat事先尝试到达目的地,并且可以很容易地首先检查是否存在(并使用cp -i/进行cp -n),但是如果期望是通过原始的裸露cp工具建立的,则可以改变行为会不必要地破坏现有脚本。毕竟,这不像现代的shell alias不能只是cp -i将默认值设置为交互式使用。
ShadowRanger

@ShadowRanger-嗯。您说得很对,我真的不知道它是容易还是很难。评论已删除。
TED

1
@ShadowRanger是的,但是那只是将艰难的一课推向了现实,直到它应用到生产系统中为止
chrylis -on

1
@sourcejedi:好玩!不会改变我的基本理论(即无条件地使用截断来打开它会更容易,并且creat恰好等于open+ O_CREAT | O_TRUNC),但是缺少O_EXCL的确解释了为什么处理现有文件并不那么容易;尝试这样做本质上是不礼貌的(您基本上必须先open/ stat检查存在性,然后使用creat,但是在大型共享系统上,到您准备creat这样做的时候,总是可能的,其他人创建了文件,现在您已经被炸毁了无论如何)。也可以无条件覆盖。
ShadowRanger

19

因为这些命令也打算在脚本中使用,可能在没有任何人工监督的情况下运行,并且还因为在很多情况下您确实想覆盖目标(Linux shell的理念是人类知道什么?他/他在做什么)

仍然有一些保护措施:

  • GNU cp有一个-n| --no-clobber选项
  • 如果将多个文件复制到一个文件中,cp则会抱怨最后一个文件不是目录。

这仅适用于特定于供应商的实施,问题不在于该特定于供应商的实施。
schily

10

是“一次做一件事”吗?

这个评论听起来像是关于一般设计原则的问题。通常,关于这些的问题是非常主观的,我们无法写出正确的答案。请注意,在这种情况下我们可能会关闭问题。

有时我们会解释原始的设计选择,因为开发人员已经撰写了有关它们的文章。但是我对这个问题没有很好的答案。

为什么cp这样设计?

问题是Unix已经有40多年的历史了。

如果现在要创建新系统,则可能会做出不同的设计选择。但是,如其他答案所述,更改Unix会破坏现有的脚本。

为什么 cp设计默默地覆盖现有文件?

简短的答案是“我不知道” :-)。

明白那cp只是一个问题。我认为没有一个原始命令程序可以防止覆盖或删除文件。重定向输出时,shell存在类似的问题:

$ cat first.html > second.html

该命令也将默默覆盖second.html

我有兴趣考虑如何重新设计所有这些程序。这可能需要一些额外的复杂性。

我认为这是解释的一部分:早期的Unix强调简单的实现有关此问题的更详细说明,请参见此答案末尾的“越差越好”。

您可以进行更改,> second.html以使其停止并显示错误(如果second.html已存在)。但是,正如我们提到的,有时用户确实希望替换现有文件。例如,她可能正在建立一个复杂的命令,尝试几次直到它执行她想要的操作。

rm second.html如果需要,用户可以先运行。这可能是一个不错的折衷方案!它本身具有一些可能的缺点。

  1. 用户必须键入两次文件名。
  2. 人们在使用时也会遇到很多麻烦rm。所以我也想变得rm更安全。但是如何?如果我们rm显示每个文件名,并要求用户确认,她现在已经写3行一个命令,而不是。另外,如果她必须经常这样做,她会养成习惯,然后键入“ y”进行确认,而不用思考。因此,这可能非常烦人,并且仍然很危险。

在现代系统上,我建议安装trash命令,并rm尽可能使用它。垃圾存储的引入是一个好主意,例如对于单用户图形PC而言

我认为了解原始Unix硬件的局限性也很重要-有限的RAM和磁盘空间,在慢速打印机上显示的输出 以及系统和开发软件。

请注意,原始Unix没有制表符完成功能,无法快速填写rm命令的文件名。(此外,原始的Bourne shell没有命令历史记录,例如,当您使用中的向上箭头键时bash)。

对于打印机输出,您将使用基于行的编辑器ed。这比视觉文本编辑器更难学习。您必须打印一些当前行,决定如何更改它们,然后键入编辑命令。

使用> second.html有点像在行编辑器中使用命令。其效果取决于当前状态。(如果second.html已经存在,其内容将被丢弃)。如果用户不确定当前状态,则应该先运行ls还是ls second.html先运行。

以“简单实现”为设计原则

Unix设计有一个流行的解释,它开始于:

设计必须在实现和接口上都简单。简单的实现比接口更重要。简洁是设计中最重要的考虑因素。

...

加布里埃尔认为,“更好的解决方案”比MIT的方法生产的软件更成功:只要初始程序基本上是好的,那么最初实施所需的时间和精力将大大减少,并且更容易适应新情况。例如,通过这种方式将软件移植到新机器变得容易得多。因此,在[更好]程序有机会开发和部署之前(先行者优势),它的使用将迅速传播。

https://en.wikipedia.org/wiki/Worse_is_better


为什么用cp“问题” 覆盖目标?以交互方式请求许可或失败可能是一个“大问题”。
库萨兰达

哇谢谢你。对指南的补充:1)编写可以做一件事并且做得很好的程序。2)信任程序员。
代数

2
@Kusalananda数据丢失是一个问题。我个人对减少丢失数据的风险感兴趣。有多种方法可以做到这一点。说这是一个问题并不意味着替代方案也没有问题。
sourcejedi '18

1
@riderdragon用C语言编写的程序通常会以非常令人惊讶的方式失败,因为C信任程序员。但是程序员并不是那么可靠。我们必须编写非常先进的工具,例如 valgrind,以尝试发现程序员所犯的错误。我认为,拥有诸如Rust或Python或C#之类的编程语言来尝试增强“内存安全性”而不信任程序员是很重要的。(C语言是由UNIX的一位作者创建的,目的是用可移植的语言编写UNIX)。
sourcejedi

1
更好的是cat first.html second.html > first.html只会给出first.html被覆盖的结果second.html。原始内容始终丢失。
doneal24 '18 -10-24

9

“ cp”的设计可以追溯到Unix的原始设计。实际上,Unix设计背后有一个连贯的哲学,它略微被嘲笑为Worse-is-Better *

基本思想是,保持代码简单实际上是比拥有完善的界面或“做正确的事”更为重要的设计考虑因素。

  • 简单性-设计必须在实现和界面上都简单。简单的实现比接口更重要。简洁是设计中最重要的考虑因素。

  • 正确性-设计在所有可观察的方面都必须正确。简单比正确要好一些。

  • 一致性-设计一定不能过分不一致。在某些情况下,为了简化起见,可以牺牲一致性,但是最好将设计中处理较少常见情况的那些部分删除,而不要引入实现复杂性或不一致性。

  • 完整性-设计必须涵盖尽可能多的重要情况。所有合理预期的情况都应包括在内。可以牺牲完整性来支持任何其他质量。实际上,每当实现简单性受到威胁时,都必须牺牲完整性。如果保持简单性,可以牺牲一致性来实现完整性。尤其是一文不值的是接口的一致性。

重点是我的

记住那是1970年,用例“ 在文件不存在的情况下我才想复制它”对于使用复制的人来说是相当罕见的用例。如果这是您想要的,您将有足够的能力在复制之前进行检查,甚至可以编写脚本。

至于为什么采用这种设计方法的OS恰好是当时赢得所有其他OS构建的OS,这篇文章的作者也对此有一个理论。

更好更好的原则的另一个好处是程序员习惯于牺牲一些安全性,便利性和麻烦,以获得良好的性能和适度的资源使用。使用New Jersey方法编写的程序在小型计算机和大型计算机上均能很好地工作,并且代码是可移植的,因为它是在病毒之上编写的。

重要的是要记住,初始病毒必须基本良好。如果是这样,则只要病毒携带方便,就可以确保病毒传播。一旦病毒传播,将有压力对其进行改进,可能是通过将其功能提高到接近90%来实现的,但是用户已经习惯了接受比正确的选择更糟糕的条件。因此,性能更好的软件首先将获得认可,其次将限制其用户的期望值,其次将其改进到几乎正确的地步。

*-或作者(但没人)称其为“新泽西方法”


1
这是正确的答案。
tchrist

+1,但我认为举一个具体的例子会有所帮助。当您安装了已编辑并重新编译(并且可能经过测试:-)的新版本程序时,您故意要覆盖该程序的旧版本。(你可能想从你的编译器相似的行为。所以,早期的Unix只有creat()VS open()open()无法创建一个文件,如果不存在的话,只需要0/1/2进行读/写/两。这还没有取O_CREAT,而且没有O_EXCL)。
sourcejedi

@sourcejedi-对不起,但作为我自己的软件开发人员,老实说,我想不出另一种方案来复制。:-)
TED

@TED抱歉,我的意思是我建议您使用此示例,因为这是您肯定要覆盖的非稀有案例之一,而您可能不想覆盖该问题。
sourcejedi

0

主要原因是GUI从定义上讲是交互式的,而类似binary的二进制文件/bin/cp只是可以在各种位置(例如,从GUI ;-)调用的程序。我敢打赌,即使在今天,对用户的绝大多数调用也/bin/cp将不会来自真正的终端,而用户会键入Shell命令,而是来自HTTP服务器,邮件系统或NAS。内置的防止用户错误的保护措施在交互式环境中是完全有意义的。在一个简单的二进制文件中就更少了。例如,您的GUI很可能会/bin/cp在后台调用以执行实际操作,并且即使它只是询问用户,也必须处理标准输出上的安全性问题!

请注意,从第一天开始,/bin/cp如果需要的话,就可以写一个安全的包装纸了。* nix的理念是为用户提供简单的构建块:/bin/cp其中之一就是。

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.