为什么我可以修改一个只读文件?

简短问题:

为什么我们可以在Vim中使用: + w + q + !来操作只读文件,即使没有管理员权限?

长问题:

我有一个文本文件(myFile.txt),对于所有人来说都是只读的:

navid@navid-ThinkPad-T530:~/ubuntuTest$ ls -l myFile.txt 
-r--r--r-- 1 navid navid 26 Aug 22 21:21 myFile.txt

我可以在没有管理员权限的情况下用Vim打开它。
navid@navid-ThinkPad-T530:~/ubuntuTest$ vi myFile.txt 

我修改它并按下:Esc + : + w + q + Enter,然后我看到这个错误信息:
E45: 'readonly' option is set (add ! to override)

到目前为止,一切都说得通。 但是当我按下:Esc + : + w + q + ! + Enter,Vim 将保存更改。
我正在使用 Ubuntu 16.04 和 VIM 7.4。

1@Zanna 你是否拥有该文件所在的目录? - Rob
是的,否则这将是一个巨大的问题 :) - Rob
11修改文件和替换文件是两种不同的操作,具有不同的权限要求。 - David Schwartz
1你可能想看一下这个。它基本上回答了你的问题,正如@DavidSchwartz正确指出的那样:修改文件和替换文件是两码事 - Panagiotis Tabakis
@PanagiotisTabakis 这真是个很好的发现,太棒了!如果你拥有该文件,可以使用chmod命令将其设置为可读写,然后再改回来。太喜欢了 :) - Rob
http://superuser.com/questions/900888/vi-can-write-to-file-despite-file-being-read-only/900924 - Anthony Geoghegan
Vim的功能。!尝试将文件的权限更改为保存您的编辑。 - waltinator
! 在只读文件系统或设备上不会覆盖。 - waltinator
7个回答

如@Rob已经提到的,只有在具有对包含文件的目录的写访问权限时才可以这样做。试图对例如/etc中的文件进行相同操作将会失败。
至于vim是如何实现这一点的,它会删除文件并重新创建。为了测试这一点,我创建了一个由root拥有的文件:
echo foo | sudo tee fff

然后按照你描述的方式,使用vim编辑文件,但同时将该过程与strace关联起来,以查看发生了什么。
strace vim fff 2> strace.out

我随后检查了strace.out并发现:
unlink("fff")                           = 0
open("fff", O_WRONLY|O_CREAT|O_TRUNC, 0644) = 4
write(4, "foasdasdao\n", 11)            = 11

所以,首先删除了文件(unlink("fff")),然后创建了一个同名的新文件(open("fff", O_WRONLY|O_CREAT|O_TRUNC, 0644)),并将我所做的修改写入其中(write(4, "foasdasdao\n", 11))。如果你在家里尝试这个操作,你会发现在用vim编辑后,该文件现在属于你而不是root用户。
严格来说,vim并不是在编辑一个你没有写权限的文件。它是从一个你有写权限的目录中删除一个文件,然后再创建一个新的文件,对于这个新文件,你同样拥有写权限。

2http://unix.stackexchange.com/questions/36467/why-inode-value-changes-when-we-edit-in-vi-editor - Gilles 'SO- stop being evil'
删除文件不被认为是写操作吗?那似乎不直观。 - CCJ
9@CCJ 这是对目录的写操作,而不是文件。对文件的写操作是指改变文件内容的操作。同样地,创建/删除文件是对目录的写操作,因为这会改变其内容。 - terdon
1strace有一个-o strace.out选项,这样你就可以将被追踪进程的stderr连接到tty上。你还可以使用-efile,write来仅追踪文件系统和写入调用,而不是mmap、信号或其他任何内容。 - Peter Cordes
2此外,这是一种危险的操作顺序。更安全的做法是将替换写入新文件名,然后使用rename(2)替换旧文件。这样就不会出现数据在磁盘上不存在的时间窗口。 - Peter Cordes
5@PeterCordes 嗯,好的。不过你可能想把你的抱怨反馈给 Vim 开发者们。我甚至都不使用这个东西,我是 Emacs 阵营的。 - terdon
我也是一个emacs用户 >.< 哦,好吧,我猜对大多数人来说它足够好用。 - Peter Cordes
@CCJ:就像大多数编程语言中可以删除const对象一样。 - Zan Lynx
2@PeterCordes Vim几乎总是在磁盘上有一个.swp文件来保护您的数据;有趣的是,看看强制关闭它是否会改变操作顺序。 - Weaver
1@PeterCordes 我相信Vim允许修改文件的方法进行更改。请参考http://vimdoc.sourceforge.net/htmldoc/editing.html#backup,尤其是http://vimdoc.sourceforge.net/htmldoc/options.html#%27backupcopy%27。 这是否与你所想的类似? - andyg0808
@andyg0808:是的,这很有道理。我以为VIM会有很多选项来控制它如何处理替换文件,因为这绝对是人们希望编辑器能正确完成的事情(根据他们自己的定义)。 - Peter Cordes
1所以基本上,文件的写入权限只有在您没有目录的写入权限时才有用。 - njzk2
3@CCJ 删除文件是对包含它的目录进行写操作,而不是对文件本身进行操作。如果你负责一个目录(即具有写权限),那么能够控制其中的内容是非常直观的,而个别文件的所有者不应该被允许覆盖你的权限。 - fkraiem
@njzk2 嗯,不是的。如果你没有对文件的写入权限,就无法对其进行写入操作。你必须删除它,并创建一个同名的新文件。这是完全不同的事情。例如,它会改变文件的权限和所有权。 - terdon
1@terdon 我不会说那是非常不同的。结果相似:有一个同名的文件,并且它里面有我的内容。 - njzk2
2这个回答听起来像是“权限模型有问题”。你说文件是只读的,但我可以任意更改内容。 - DeadMG
权限模型完全没问题,你只需要为父目录设置正确的权限即可。 - til_b
2@DeadMG 权限模型是没问题的,你只是误解了它的工作原理。如果你对一个目录有写入权限,那意味着你完全可以在该目录中创建和删除文件。这是正常的,所有权限系统都是如此。因此,你可以删除和重新创建文件。但是你不能修改文件。如果你想将文件设置为只读,就不要将它放在可写入的目录中。抱怨这个系统有问题就像抱怨当你把钥匙给别人时,他们打开了你的保险箱一样。 - terdon

使用w!命令,你可以删除原始文件(在允许的情况下),并将你的版本写入其中。 当你对一个目录具有写权限时,你可以在该目录中创建、移动或删除文件。
$ mkdir foo
$ echo hi > foo/file
$ chmod 777 foo
$ chmod 700 foo/file
$ ls -l foo/file 
-rwx------ 1 ravexina ravexina 7 Aug 31 03:19 foo/file

现在让我切换用户并更改文件。
$ sudo -u user2 -s
$ vi foo/a # save using w! (I wrote into the file bye)
$ ls -l foo/a
-rwx------ 1 user2 user2 7 Aug 31 03:20 foo/file

现在看看里面有什么:
$ cat foo/file
bye

只要你拥有父目录,无论权限如何,你都可以删除或替换文件,因为你可以更改目录的内容 :).
尝试使用其他命令,比如rm,它会提示你,但你仍然可以执行操作。将目录设置为不可写,这样就可以阻止它。
补充说明:
刚刚尝试了一下,只要我拥有该文件,即使文件夹只读,我仍然可以修改它。然而,当我将所有权更改为root:root时,它无法打开文件进行写入。因此,解决了修改由root(或其他人)拥有的文件的问题。

7听起来VIM可以选择多种策略,包括原地重写或者解除链接+写入新文件。 - Peter Cordes
3@PeterCordes 是的,它显然会非常努力地按照你的指示去做 :) 真是个聪明的家伙。 :) - Rob

查看:help write-readonly
                                                        write-readonly
When the 'cpoptions' option contains 'W', Vim will refuse to overwrite a
readonly file.  When 'W' is not present, ":w!" will overwrite a readonly file,
if the system allows it (the directory must be writable).

由于您对目录拥有写入权限(也就是可以在其中创建、删除或重命名文件),系统允许这样做。

cpoptions 的默认值不包含 W

                                                'cpoptions' 'cpo' cpo
'cpoptions' 'cpo'       string  (Vim default: "aABceFs",
                                 Vi default:  all flags)
                        global

无论是您的vim编辑器进程还是您的文件,都承载着您的个人风格和偏好。
 getpwnam("navid")->pw_uid

拥有权,这样你也可以支付。
 :!chmod +w %

你可能猜得到,曾经有一个更简单的时代。
 :!rm %

在 . 目录下,只需要 +w,u-t 的解除链接权限,而不需要所有权,这种操作变得太频繁了,以至于有人觉得输入这样的命令太麻烦。因此,vim 被重新编程,可以自动提供并在请求时自动执行此类操作。

试着覆盖你姐姐的文件吧。

 /home/whoopi/.profile

作为一个简单的导航和赌注,你的活力给了你想要的拒绝。

感谢 @Zanna 对代码进行编辑,尽管这让我这个新手失去了一点声望,但是确实是应该的。 - Roman Czyborra

这是VIM对你的警告,考虑到UNIX中的权限如何工作,这可能是相对重要的。这种看似不直观的原因是因为UNIX文件系统在文件的i-node中存储有关权限的信息。目录结构与此有所区别,并且仅链接这些i-node。目录也有自己的权限,确定是否可以将文件链接/取消链接到其中,读取它或遍历子目录。这种设计允许同一个文件在目录结构中的多个不同位置出现(通过硬链接)。通过说 "add ! to override ",VIM试图警告你,原始文件将被取消链接(因此它将保持在所有其他位置不变),而新文件将被创建并链接到目录结构中的原始位置。如果原始文件的链接计数减少到零,则原始文件将被释放,但如果没有,则实际上是在克隆文件。打开文件也算作链接,因此如果某个程序打开了该文件,并且你同意 "add ! to override",那么该程序将无法看到你使用VIM对文件所做的更改。文件只会被VIM从目录中取消链接,在另一个程序关闭文件之后,该文件将被释放,除非它已在其他地方链接。
请注意,在Windows中,文件的权限存储在目录中,因此从Windows权限范例的角度来看,这种vim行为可能确实看起来很奇怪。对于写入文件,Windows逻辑上也可能检查一些目录权限,甚至是超级目录权限。如上所述,在UNIX中,目录权限对于操作文件来说是无关紧要的,只要您能够列出它并打开它(即所有超级目录都有x权限)。在UNIX中打开的文件甚至可能不再具有文件名,如果在打开后从所有目录中取消链接。
例如,您有文件/home/user1/foo,并且它与/home/user2/foo是相同的文件(即硬链接),该文件不可由任何人写入,并且当前由程序P打开(由root启动的程序以读写方式打开)。如果user1使用vim打开它并进行覆盖,他会创建自己的副本,并且不再看到原始文件。如果随后user2使用vim打开他的链接并写入其中,它将再次被取消链接,并且他将创建另一个副本。程序P仍然可以看到原始文件,并且可以自由地读取或写入其中。一旦程序关闭文件,文件将消失(被文件系统释放)。

这并不是一个完全的答案,但如果你真的想要设置一个文件,以便任何人都无法更改或删除它,你可以将其设为不可变。

通常情况下,即使一个文件是由超级用户(root)拥有的,只要你对文件夹有写权限,你仍然可以删除该文件。但是当你将文件设为不可变时,即使超级用户(root)也无法修改或删除它。

要将文件设为不可变(需要使用sudo命令):

sudo chattr +i myFile.txt

你可以用lsattr命令来查看(结果中的字母i):
$ lsattr myFile.txt
----i--------e-- myFile.txt

使文件恢复正常的方法是:
sudo chattr -i myFile.txt

澄清一下:当文件被设为不可变时,它无法被删除、重命名、修改,甚至无法进行硬链接。
值得一读的是`man chattr`,因为文件可以具有许多有用的属性。
你可能还会发现"受限删除"很有用。如果应用于一个文件夹(而不是文件),这意味着在该文件夹内创建文件的人可以修改或删除该文件,但其他人(除了root)则不能。文件夹`/tmp`已设置了此标志。你可以通过`/tmp`上的`t`标志来查看。
$ ls -l --directory /tmp
drwxrwxrwt 10 root root 4096 Sep  6 09:00 /tmp

设置或移除文件夹的受限删除标志:
chmod +t myFolder      # Add the restricted deletion flag.
chmod -t myFolder      # Remove the restricted deletion flag.