nano无法保存:权限被拒绝的4个原因
nano保存失败通常不是编辑器故障。本文按顺序检查文件所有者、父目录权限、只读或空间不足的文件系统,以及容器中的用户ID,并说明何时应使用sudoedit。
nano 无法保存文件的原因
nano 无法保存文件通常有以下4种原因:您不是文件的所有者;父目录不允许 nano 执行所需操作;文件系统为只读或空间不足;或者您在以不同用户 ID 运行的容器中。前两种属于权限问题,后两种不属于权限问题。请按此顺序检查,因为第一种原因覆盖了大多数情况,只需一条命令即可确认,而且对应的修复方法是 sudoedit,而不是 sudo nano。
只要编辑器仍处于打开状态,就不会丢失任何内容。文本仍保存在内存中,因此您可以保持文件打开,将缓冲区写入您拥有权限的路径,之后再将其放回原位置。本指南接近末尾处会介绍这种应急方法。
在修改任何权限前运行这些检查
将每条命令都指向你要编辑的实际路径。这些命令回答的问题不同,因此在进行任何操作前都应全部运行。还没确认哪个检查失败就修改权限,通常会在原有问题之上再引入一个问题。
id
ls -l /etc/nginx/nginx.conf
ls -ld /etc/nginx
namei -l /etc/nginx/nginx.conf
findmnt -no SOURCE,FSTYPE,OPTIONS -T /etc/nginx/nginx.conf
df -h /etc/nginx
df -i /etc/nginxid显示当前用户 ID 和所属的组 ID。ls -l显示文件本身的所有者、组和权限位。ls -ld显示包含该文件的目录的所有者、组和权限位;这是另一个独立的问题,结果也可能不同。namei -l遍历路径的每一部分,并列出每一部分的所有者和权限,因此一次输出即可回答这两个问题。findmnt显示该路径所在的文件系统及其挂载选项。df -h报告可用空间,df -i报告可用 inode 数量;inode 可能会独立于空间耗尽。如果你还不熟悉这些权限字符串,请先阅读如何解读 ls -l 输出的权限字符串。
原因 1:文件属于 root,而您不是 root
读取和写入是两项独立的权限,大多数位于 /etc 下的文件都允许所有用户读取。因此,nano 可以打开文件、显示内容,并允许您自由输入,因为这些操作都不会写入磁盘。保存时才会被拒绝:此时内核会根据文件的所有者、所属组和其他用户权限位,将您的用户 ID 和组 ID 与这些权限进行比较。nano 只是转述内核返回的信息,因此修改 nano 选项不会改变结果。
id 和 ls -l 共同说明了原因。文件属于 root,您不是 root,且其他用户权限位未授予写权限。再次按 Ctrl-O 也无法解决问题。
sudoedit 是编辑 root 所有文件的正确方式
SUDO_EDITOR=nano sudoedit /etc/nginx/nginx.confsudo 会创建该文件的临时副本,并将所有者设为您;然后以您自己的用户身份在该副本上运行 nano,编辑器退出后,再以 root 权限将结果复制回原位置。编辑器始终不会以 root 身份运行。sudo -e 是同一命令的另一个名称。编辑器依次从 SUDO_EDITOR、VISUAL 和 EDITOR 中选择,因此在 shell 配置文件中设置 export EDITOR=nano 后,它会在所有位置成为默认编辑器。如果 sudoers 中的 env_editor 标志已关闭,则会忽略这些变量,编辑器改用 sudoers 中的 editor 设置。
sudo nano 同样可以保存文件,这正是问题所在。在整个会话期间,它会让完整的交互式编辑器获得针对整个文件系统的 root 权限。因此,在保存提示符中误输入路径,就可能以 root 身份将文本写入其他系统文件。养成 以普通用户身份工作,只在需要的步骤中调用 sudo 的习惯很重要;编辑配置文件时,sudoedit 正是这种习惯的体现。
sudoedit 有两条容易让人意外的规则。它拒绝编辑符号链接,也拒绝编辑位于您可写目录中的文件,除非您是 root。第二条规则的原因是,任何能够写入该目录的人,都可以在编辑器打开文件期间替换该文件。这两种行为都是 sudoers 的默认设置(sudoedit_follow 关闭,sudoedit_checkdir 开启)。如果文件尚不存在,sudoedit 会为您创建该文件。
原因 2:父目录实际控制的内容
针对其他编辑器编写的建议通常会说,保存文件需要目录具有写权限,因为许多编辑器会通过写入新文件,再将其重命名覆盖旧文件来保存。nano 不是这样工作的。它会打开你指定的文件,并直接写入该文件。因此,对于已经存在的文件,系统不会检查目录的写权限位。
目录仍然决定其他操作,这也是 ls -ld 出现在检查清单中的原因:
- 创建尚不存在的文件需要目录具有写权限和执行权限,因为必须向目录中添加新的名称。你的 umask 决定新文件初始具有的权限。
- 要访问文件,需要路径中每一级目录都具有执行权限,也称为搜索权限。任何一级目录缺少该权限,下面的所有内容都会无法访问;
namei -l会显示缺少权限的目录。 - 启用备份或文件锁定后,保存操作会在原文件旁边写入第二个文件,因此这些功能确实需要目录具有写权限。备份选项是 nanorc 中的
-B或set backup,锁定选项是-G或set locking。除非你或发行版启用了它们,否则这两项都处于关闭状态。
目录权限在系统的其他位置同样重要。当你的 home 目录或 .ssh 目录可被其他用户写入时,SSH 服务器会拒绝使用你的密钥。这是 SSH 在登录时拒绝你的密钥 的常见原因之一。
由于 nano 会直接写入已存在的文件,该文件会保留其 inode,也就是磁盘上对应此名称的身份标识。任何已打开该文件的进程都会继续跟踪它,绑定挂载到容器中的单个文件也会继续正常工作。通过替换文件来保存的编辑器会破坏该挂载,因为挂载跟踪的是 inode,而不是文件名。
原因 3:文件系统为只读,或已无可用空间
findmnt 报告 ro 表示该写入从一开始就不可能成功。文件系统可能是通过 /etc/fstab 或只读 bind mount 以这种方式挂载的,也可能是磁盘错误后由内核重新挂载为只读。后一种情况更严重。sudo dmesg -T | tail -50 会显示导致重新挂载的输入/输出错误和文件系统错误。修复方法是在文件系统未挂载时执行文件系统检查。对于 VPS,这意味着启动服务商提供的救援控制台。
文件系统已满时,同一个写入操作也会因另一种原因失败。df -h 介绍常见情况。df -i 介绍容易被忽略的情况:inode 来自创建文件系统时分配的固定池。包含大量小文件的目录树可能耗尽所有 inode,而 df -h 仍显示有数 GB 可用空间。空间耗尽且没有明显占用者时,df 与 du 对满磁盘的报告不一致介绍了导致该问题的已删除但仍处于打开状态的文件。
这里还有一个细节可以解释一种容易混淆的现象。创建 ext4 文件系统时,系统会为 root 保留一部分块,因此普通用户的写入被拒绝后,root 仍可继续写入。此时 sudo 看起来像是修复方法,磁盘随后填满剩余空间,问题会以更严重的形式再次出现。
nano 会在写入新内容前截断文件,因此写入过程中途耗尽空间时,文件可能会比原来更短。在接近满载的文件系统上编辑重要配置前,请先复制一份配置文件。sudo cp -a /etc/nginx/nginx.conf /root/nginx.conf.bak 会在副本中保留所有者、组和权限。
原因 4:您正在容器中编辑绑定挂载
文件所有权以数字形式存储。内核保存的是用户 ID,您看到的名称来自执行查询的 /etc/passwd,因此同一个文件在主机上可能显示一个名称,在容器中显示另一个名称,或者只显示数字。请比较数字,而不是名称:在容器中运行 id -u,并对该文件运行 ls -ln。
绑定挂载的文件会保留其在主机上的所有权。当主机文件属于您的用户,而容器进程以另一个用户身份运行时,容器内的写入会被拒绝。在容器内运行 sudo 也不会更改主机上的所有者。请在主机上将所有者设置为容器进程使用的 ID,或者让容器以当前文件所有者的 ID 运行。linuxserver.io 及类似项目提供了 用于设置进程运行身份的 PUID 和 PGID 变量。
还需要了解另外两种容器情况。使用 :ro 将挂载设置为只读,或使用 --read-only 启动容器后,无论所有权如何,写入都会被拒绝;在容器内运行 cat /proc/mounts 可查看该标志。使用 rootless Podman 时,用户命名空间会将容器用户 ID 映射到主机 ID 范围,因此在容器内看似属于 root 的文件,在容器外实际属于您的非特权账户。
还有一种情况是编辑成功,但随后修改消失。您在容器内修改的文件,如果所在路径不是挂载点,就会存储在该容器的可写层中;重新创建容器时,这一层会被丢弃。如果修改需要长期保留,请在主机端的挂载路径中修改文件,或在构建镜像时修改文件。
应急方案:将文件保存到您拥有的路径
不要尝试从编辑器内部获取权限。按下 Ctrl-O,在提示符处清除路径,输入主目录下的路径,例如 /home/you/nginx.conf.new,然后按 Enter。接着按 Ctrl-X 退出。现在文件已保存到磁盘,由您拥有,剩下的只是普通的文件复制操作。
sudo cp /home/you/nginx.conf.new /etc/nginx/nginx.conf
sudo nginx -t此处应使用 cp,而不是 mv。cp 会直接写入现有文件,因此该文件会保留原有的所有者、组和权限。mv 会在同一文件系统中用您的文件替换现有文件,结果是 /etc 中的配置文件归您的用户帐户所有,这会成为您需要解决的下一个权限问题。
在重新加载任何服务之前,先使用负责该文件的工具检查结果。sudo nginx -t 会解析 nginx 配置,sudo sshd -t 会解析 SSH 服务器配置。有两个文件使用专用编辑器即可完成整个流程:sudo visudo 用于 /etc/sudoers,crontab -e 用于您自己的 cron 作业。它们都会编辑临时副本、检查语法,并且仅在解析成功后安装文件。
FAQ
应使用 sudo nano 还是 sudoedit 编辑系统文件?
使用 sudoedit。它会将文件复制到由您拥有的临时副本中,以您的用户身份运行编辑器,并在编辑器退出后以 root 身份写回结果。因此,编辑器本身始终不会获得 root 权限。将 SUDO_EDITOR、VISUAL 或 EDITOR 设置为 nano,即可选择 nano。sudo nano 也可以使用,但它会在会话期间让交互式编辑器以 root 身份访问系统中的所有路径。这样一来,在保存提示符中输错一个文件名,就可能损坏系统文件。
使用 nano 保存文件时,是否需要对目录具有写入权限?
对于已经存在的文件,不需要。nano 会直接写入文件本身,因此内核会检查文件的写入位,以及路径中每个目录的执行位。当文件尚不存在时,目录的写入位才会生效,因为此时必须创建一个新名称。如果启用了备份或文件锁定,目录的写入位同样重要,因为这两项功能都会在原文件旁边写入第二个文件。
文件所有者看起来正确,磁盘也未满。还有什么会阻止写入?
有4种情况。文件系统可能以只读方式挂载,findmnt -no OPTIONS -T /etc/nginx/nginx.conf 可以显示这一点。文件可能带有 immutable 属性,lsattr 可以显示该属性,sudo chattr -i 可以移除它;该属性启用时,即使 root 也无法写入文件。inode 池可能已经耗尽,即使仍有可用空间;df -i 可以显示这一点。SELinux 或 AppArmor 可能拒绝写入,即使权限位允许写入;审计日志会记录针对您尝试访问路径的拒绝事件。
文件完全无法保存时,应将更改放在哪里?
按 Ctrl-O,然后指定一个您拥有的路径,可以位于您的主目录下,也可以位于用户具有写入权限的其他位置。缓冲区仍保存在内存中,因此您输入的内容不会丢失。之后使用 sudo cp 将保存的文件复制到目标位置。该命令会保留原文件的所有者和权限。然后使用服务自身的测试命令检查文件,再重新加载服务。
为什么我在 Docker 容器中的编辑内容会消失?
如果该路径不是挂载点,编辑内容会写入容器的可写层;容器被替换时,该层也会被丢弃。请在 bind mount 或 volume 的主机端编辑文件,或者将文件构建到镜像中。如果该路径是 bind mount,但保存操作被拒绝,请比较容器内的 id -u 与 ls -ln 显示的数字所有者:文件会保留主机上的所有权,容器进程必须使用匹配的用户或组。