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

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

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遵循这种模式而不是默默地覆盖文件?

答案1

的默认覆盖行为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 系统接口卷中定义的 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 被开发出来时,与现代计算机相比,系统的内存和大容量存储空间非常小,覆盖警告和提示可能被视为浪费和不必要的奢侈。

在编写 POSIX 标准时,先例已经牢固确立,标准的编写者很清楚 POSIX 标准的优点不破坏向后兼容性

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

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

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

如果您也在脚本中强制提示,那么当在没有用户的上下文(例如后台进程或 cron 作业)中运行时,该命令会做什么?脚本会挂起、中止或覆盖吗?

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

命令行的很大一部分力量来自于以下事实:一旦您知道如何在命令行上执行某些操作,您就会隐式地知道如何通过编写脚本使其自动发生。但只有当您以交互方式使用的命令在脚本上下文中调用时也能完全相同时,这才是正确的。交互式使用和脚本使用之间的行为上的任何显着差异都会产生一种令高级用户烦恼的认知失调。

答案2

cp源自 Unix 之初。它早在 Posix 标准编写之前就已经存在了。确实:Posix 只是正式化了cp这方面的现有行为。

我们谈论的是纪元(1970-01-01),那时男人是真正的男人,女人是真正的女人和毛茸茸的小生物......(我离题了)。在那些日子里,添加额外的代码会使程序变得更大。这在当时是一个问题,因为第一台运行 Unix 的计算机是 PDP-7(可升级到 144KB RAM!)。所以东西很小,效率很高,没有安全功能。

所以,在那些日子里,你必须知道自己在做什么,因为计算机没有能力阻止你做任何事后后悔的事情。

(Zevar 有一幅漂亮的漫画;搜索“zevar cerveaux Assiste par ordordur”即可找到计算机的演变。或者尝试http://a54.idata.over-blog.com/2/07/74/62/dessins-et-bd/le-CAO-de-Zevar---reduc.jpg只要它存在)

对于那些真正感兴趣的人(我在评论中看到了一些猜测):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 行!

答案3

因为这些命令也旨在在脚本中使用,可能在没有任何人类监督的情况下运行,而且还因为在很多情况下您确实想要覆盖目标(Linux shell 的哲学是人类知道什么)她/他正在做)

还有一些保障措施:

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

答案4

“cp”的设计可以追溯到Unix最初的设计。事实上,Unix 设计背后有一个连贯的哲学,它比半开玩笑地称为的要稍微少一些。更坏就是更好*

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

  • 简单性——设计必须简单,无论是实现还是接口。实现简单比接口更重要。简单性是设计中最重要的考虑因素。

  • 正确性——设计在所有可观察到的方面都必须是正确的。简单比正确稍微好一些。

  • 一致性——设计不能过于不一致。在某些情况下可以为了简单性而牺牲一致性,但是最好放弃那些处理不太常见情况的设计部分而不是引入实现复杂性或不一致。

  • 完整性——设计必须涵盖尽可能多的实用情况。应涵盖所有合理预期的情况。为了任何其他质量,可以牺牲完整性。事实上,每当危及实现简单性时,就必须牺牲完整性。如果保留简单性,则可以牺牲一致性来实现完整性;尤其没有价值的是界面的一致性。

强调我的

请记住,这是 1970 年,“我想复制此文件”的用例仅有的对于执行复制的人来说,“如果它不存在”将是一个相当罕见的用例。如果这就是您想要的,您将非常有能力在复制之前进行检查,甚至可以编写脚本。

至于为什么采用这种设计方法的操作系统恰好胜过了当时正在构建的所有其他操作系统,该文章的作者对此也有自己的理论。

“更坏就是更好”理念的另一个好处是,程序员习惯于牺牲一些安全性、便利性和麻烦来获得良好的性能和适度的资源使用。使用新泽西方法编写的程序在小型机器和大型机器上都可以很好地运行,并且代码将是可移植的,因为它是在病毒之上编写的。

重要的是要记住,最初的病毒必须基本上是好的。如果是这样,只要它是可携带的,病毒传播就可以保证。一旦病毒传播,就会面临改进它的压力,可能是将其功能提高到接近 90%,但用户已经习惯于接受比正确的更糟糕的事情。因此,“越差越好”的软件首先会获得接受,其次会降低用户的期望,第三会改进到几乎正确的程度。

* - 或者是作者(但没有其他人)所说的“新泽西方法”

相关内容