vim如何使用"sudo写入"技巧?

1713

你们中的许多人可能已经看到过一条命令,它允许你在需要root权限的文件上进行写操作,即使你忘记使用sudo打开vim:

:w !sudo tee %

问题是我不太明白这里具体发生了什么。

我已经弄清楚了这个: w是用于这个目的的。

                                                        *:w_c* *:write_c*
:[range]w[rite] [++opt] !{cmd}
                        Execute {cmd} with [range] lines as standard input
                        (note the space in front of the '!').  {cmd} is
                        executed like with ":!{cmd}", any '!' is replaced with
                        the previous command |:!|.

它将所有行作为标准输入传递。

!sudo tee 部分以管理员特权调用 tee

为了让所有的东西都有意义,% 应该输出文件名(作为 tee 的参数),但我在帮助文档中找不到这种行为的参考。

tl;dr 有人能帮我分析一下这个命令吗?


9
@Nathan::w!sudo cat >%是否同样适用,且不会污染标准输出? - Bjarke Freund-Hansen
65
@bjarkef - 不行,这样的话sudo只会应用于cat命令,而不是>符号,所以不能通过。你可以尝试在一个sudo子shell中运行整个命令,例如:w !sudo sh -c "cat % > yams.txt",但这也行不通,因为在子shell中,%是空的,你会清空文件的内容。 - Nathan Long
3
我想补充一点,输入该命令后,可能会出现一个警告信息。如果出现,请按L键。然后,你将被要求按回车键。执行操作,最终文件将被保存。 - pablofiumara
14
@NathanLong @knittl: :w !sudo sh -c "cat >%" 的效果与 sudo tee % 一样,因为Vim在传递给子shell之前就已经将文件名代替了 % 。但是,如果文件名中有空格,两者都不起作用;必须使用 :w !sudo sh -c "cat >'%'":w !sudo tee "%" 来解决这个问题。 - Han Seoul-Oh
5
使用 :W 命令保存并重新加载文件: 命令 W:执行 ':silent w !sudo tee % > /dev/null' | :edit! - Diego Roberto Dos Santos
显示剩余6条评论
10个回答

1936

:w !sudo tee %中...

%指的是“当前文件”

正如eugene y所指出的那样%确实意味着“当前文件名”,它会传递给tee,以便它知道要覆盖哪个文件。

(在替换命令中,略有不同;正如:help :%所示,它等于1,$(整个文件)(感谢@Orafu指出这并不等于文件名)。例如,:%s/foo/bar的意思是“在当前文件中,将foo替换为bar。” 如果在键入:s之前突出显示一些文本,则突出显示的行将代替%作为您的替换范围。)

:w没有更新您的文件

这个技巧令人困惑的一部分是你可能会认为:w正在修改你的文件,但它没有。如果您打开并修改了file1.txt,然后运行:w file2.txt,它将是一个“另存为”;file1.txt不会被修改,但当前缓冲区内容将被发送到file2.txt

您可以替换一个 shell 命令来接收缓冲区内容。例如,:w !cat只会显示内容。

如果 Vim 没有以 sudo 权限运行,则它的:w无法修改受保护的文件,但是如果它将缓冲区内容传递给 shell,则 shell 中的命令将可以使用 sudo 运行。在这种情况下,我们使用tee

了解 tee

至于tee,在正常的 bash 管道情况下,可以将tee命令想象成一个T形管道:它将输出定向到指定的文件,并将其同时发送到标准输出,后者可以被下一个管道命令捕获。

例如,在ps -ax | tee processes.txt | grep 'foo'中,进程列表将被写入文本文件传递给grep

     +-----------+    tee     +------------+
     |           |  --------  |            |
     | ps -ax    |  --------  | grep 'foo' |
     |           |     ||     |            |
     +-----------+     ||     +------------+
                       ||   
               +---------------+
               |               |
               | processes.txt |
               |               |
               +---------------+

(此图是使用 Asciiflow 创建的。)

更多信息请参见tee man页面

使用 tee 的 hack 技巧

在您所描述的情况下,使用 tee 是一种 hack 技巧,因为我们忽略了它的一半作用sudo tee 将内容写入文件并将缓冲区内容发送到标准输出,但是我们忽略了标准输出。在这种情况下,我们不需要将任何内容传递给另一个管道命令;我们只是使用 tee 作为替代方法来写入文件,以便我们可以使用 sudo 调用它。

使此技巧易于使用

您可以将以下内容添加到您的 .vimrc 中,使此技巧易于使用:只需键入 :w!! 即可。

" Allow saving of files as sudo when I forgot to start vim using sudo.
cmap w!! w !sudo tee > /dev/null %
> /dev/null这部分明确地丢弃了标准输出,因为我们不需要将任何东西传递给另一个管道命令。

186
特别喜欢你的表示法"w !!",在使用命令行上的"sudo !!"之后很容易记住。 - Aidan Kane
14
所以这里使用了 tee,因为它可以将标准输入写入文件。我很惊讶没有一个专门做这个工作的程序(我发现了一个我从未听说过的叫做 sponge 的程序可以实现这个功能)。我猜典型的“将流写入文件”的操作是由 shell 内置命令执行的。Vim 的 !{cmd} 不会分叉出一个 shell(而是分叉出 cmd)吗?也许更明显的做法是使用一些可用的变体 sh -c ">",而不是 tee - Steven Lu
2
@Steven Lu:spongemoreutils软件包的一部分,几乎在除了Debian以外的所有发行版上都有。moreutils有一些非常好用的工具,与更常见的工具如xargstee不相上下。 - Swiss
10
如何扩展此别名,以便告诉vim还要自动将更改后的文件内容加载到当前缓冲区?它要求我这样做,如何自动化它? - Zlatko
10
在这种情况下,cat 以 root 用户身份运行,并且输出被 shell 重定向,而 shell 并不是以 root 身份运行。这与 echo hi > /read/only/file 的情况相同。 - WhyNotHugo
显示剩余18条评论

116
在执行的命令行中,%代表当前文件名。这在:help cmdline-special文档中有记录。
In Ex commands, at places where a file name can be used, the following
characters have a special meaning.
        %       Is replaced with the current file name.

如您已经了解,:w !cmd 将当前缓冲区的内容传输给另一个命令。 tee 的作用是将标准输入复制到一个或多个文件,并输出到标准输出。因此,:w !sudo tee % > /dev/null 的实际效果是在以 root 身份的情况下将当前缓冲区的内容写入当前文件中。可以使用的另一种命令是dd
:w !sudo dd of=% > /dev/null

作为一种快捷方式,您可以将此映射添加到您的.vimrc中:
" Force saving files that require root permission 
cnoremap w!! w !sudo tee > /dev/null %

通过上述步骤,您可以键入 :w!!<Enter> 以 root 身份保存文件。


1
有趣的是,:help _% 可以带出你输入的内容,但 :help % 则会带出大括号匹配键。我本来不会想到尝试下划线前缀,这在 vim 文档中是一种模式吗?在寻找帮助时还有其他“特殊”的东西可以尝试吗? - David Pope
2
@David:help 命令跳转到标签。您可以使用 :h help-tags 查看可用标签。您还可以使用命令行完成来查看匹配的标签::h cmdline<Ctrl-D>(如果您相应地设置了 wildmode,则为 :h cmdline<Tab>)。 - Eugene Yarmash
1
我不得不在我的 .vimrc 文件中使用 cmap w!! w !sudo tee % > /dev/null 使其工作。上面的答案中 % 是否放错了位置?(我不是 vim 专家。) - dmmfll
@DMfll 是的,没错。回答中的命令会导致 sudo tee > /dev/null /path/to/current/file,这并没有实际意义。(我会进行编辑) - jazzpi
1
@jazzpi:你错了。Shell 实际上并不关心你在命令行中进行文件重定向的位置。 - Eugene Yarmash
@eugeney 哦,抱歉,我不知道那样可以。只有在中间放置 tee 管道时才有效。仍然很奇怪为什么对 @DMfll 不起作用。 - jazzpi

29
接受的答案已经把所有内容都讲清楚了,所以我只是为了记录提供一个我使用的快捷方式的另一个例子。

将它添加到你的etc/vim/vimrc(或者~/.vimrc)中:

  • cnoremap w!! execute 'silent! write !sudo tee % >/dev/null' <bar> edit!


其中:

  • cnoremap:告诉vim接下来的快捷方式要与命令行相关联。
  • w!!:快捷方式本身。
  • execute '...':执行以下字符串的命令。
  • silent!:静默运行
  • write !sudo tee % >/dev/null:OP问题,添加了消息重定向到NULL,使命令更加干净。
  • <bar> edit!:这个技巧就像蛋糕上的樱桃:它还调用edit命令重新加载缓冲区,然后避免出现缓冲区已更改等消息。 <bar>是分隔两个命令的管道符号的写法。


希望对你有所帮助。另外还可以查看其他问题:


静默!禁用了密码提示,所以您看不到它。 - Andy Ray

20

这也很有效:

:w !sudo sh -c "cat > %"

这是受到@Nathan Long的评论启发。

注意:

"必须用来代替',因为我们要在传递给shell之前扩展%


15
虽然这样做可能有效,但也会授予多个程序(sh和cat)sudo访问权限。通过将“tee”替换为“/usr/bin/tee”,可以提高其他示例的安全性,以防止路径修改攻击。 - idbrii

19

:w - 写入文件。

!sudo - 调用shell的sudo命令。

tee - 使用tee命令重定向vim :w命令的输出。%代表当前文件名,即/etc/apache2/conf.d/mediawiki.conf。也就是说,tee命令作为root用户运行,接收标准输入并将其写入以%表示的文件中。但是,这将提示重新加载文件(在vim本身中按 L 来加载更改):

教程链接


10
我想提出另一种解决“哎呀,我在打开文档时忘了输入sudo”的方法:
与其收到“权限被拒绝”的错误提示并输入:w!!,我认为更加优雅的做法是使用有条件的vim命令。如果文件所有者是root,则执行sudo vim
这个实现非常简单(可能还有更加优美的实现方式,但我显然不是bash大师)。
function vim(){
  OWNER=$(stat -c '%U' $1)
  if [[ "$OWNER" == "root" ]]; then
    sudo /usr/bin/vim $*;
  else
    /usr/bin/vim $*;
  fi
}

它的效果非常好。

这是一种更加以bash为中心而不是以vim为中心的方法,因此并不是每个人都会喜欢。

当然:

  • 有些情况下它会失败(例如文件所有者不是root但需要sudo,但该函数仍可进行编辑)
  • 如果只是使用vim来只读一个文件,这样做就没有意义了(就我而言,我会使用tailcat来处理小文件)

但我发现这带来了更好的开发用户体验,这在使用bash时往往被忽视。 :-)


6
请注意这个过程不太容易纠正错误。就我个人而言,我的大多数错误都是愚蠢的错误。因此,当我做某些可能既愚蠢又重要的事情时,我更喜欢提前得到警告。当然,这是一种偏好,但权限提升应该是一个有良心的行为。另外:如果您经常遇到这种情况,使“:w!!”变得非常麻烦以至于需要自动使用sudo(但仅在owner = root时);您可能需要检查一下当前的工作流程。 - Payne
1
有趣的评论,不过需要注意,当以“root”身份打开文件时,会要求输入密码。 - Augustin Riedinger
啊!这就是区别所在。 这取决于sudo用户是否设置了“NOPASSWD”。 - Payne
3
拥有NOPASSWD 就是相对不太宽容的做法... :) - Augustin Riedinger
顺便提一下,如果有意识问题,可以额外添加一个 read -p "Owner is root. Do you want to open the file with sudo?" -n 1 -r - Augustin Riedinger

7

对于NEOVIM

由于交互调用存在问题 (https://github.com/neovim/neovim/issues/1716),我使用了基于Dr Beco的答案来处理neovim:

cnoremap w!! execute 'silent! write !SUDO_ASKPASS=`which ssh-askpass` sudo tee % >/dev/null' <bar> edit!

这将使用 ssh-askpass 打开对话框,询问 sudo 密码。


我很感谢neovim的考虑。不过出于某些原因,这对我并没有起作用。我找到了一个解决问题的插件。这是一个相对不太容易实现但结果很好的成就。https://github.com/lambdalisue/suda.vim - E - Edmund's Echo

4

以下是我在2020年发现的最常见答案的简要总结(并进行了一些微小改进)。

简要概述

使用 :w!!:W!! 命令,并在它展开后按下 enter 键。

  • 如果你在输入 w/W 后输入 !! 的速度太慢,它将不会展开,并可能会报告: E492: Not an editor command: W!!

注意: 如果你的情况与之不同,请使用 which tee 输出替换 /usr/bin/tee

将下面这些内容放入你的 ~/.vimrc 文件中:

    " Silent version of the super user edit, sudo tee trick.
    cnoremap W!! execute 'silent! write !sudo /usr/bin/tee "%" >/dev/null' <bar> edit!
    " Talkative version of the super user edit, sudo tee trick.
    cmap w!! w !sudo /usr/bin/tee >/dev/null "%"

更多信息:

首先,下面链接的回答似乎是唯一一个在大部分已知问题上缓解了问题并与其他回答有所不同的值得阅读: https://dev59.com/TXVD5IYBdhLWcg3wXaid#12870763

我的回答以上汇总了传统sudo tee主题的多个建议,并在我找到的最常见的答案上略微改进。我的版本如下:

  • 适用于文件名中包含空格的情况

  • 通过指定tee的完整路径来减轻路径修改攻击。

  • 提供两种映射:W !!表示静默执行,w !!表示非静默执行,即多嘴 :-)

  • 使用非静默版本的区别在于,你可以选择[O]k和[L]oad之间的差异。如果你不关心,请使用静默版本。

    • [O]k - 保留你的撤消历史记录,但会在你尝试退出时给你警告。你必须使用:q!来退出。
    • [L]oad - 删除你的撤消历史记录并重置"修改标志",允许你退出而不被警告保存更改。

上述信息来自于其他答案和评论,但特别是:

Dr Beco的回答:https://dev59.com/uXE85IYBdhLWcg3wzm5p#48237738

idbrii对此的评论:https://dev59.com/uXE85IYBdhLWcg3wzm5p#25010815

Han Seoul-Oh对此的评论:How does the vim "write with sudo" trick work?

Bruno Bronosky对此的评论:https://serverfault.com/a/22576/195239

此答案还解释了为什么表面上最简单的方法并不是一个好主意: https://serverfault.com/a/26334/195239


2
cnoremap w!! 的唯一问题是,当您在 : 命令提示符处键入 w! 时(并等待您键入下一个字符),它会将 w 替换为 !。例如,当您要使用 w! 进行强制保存时。而且,即使它不是紧跟着 : 的第一件事情,它也会影响到其他命令。
因此,我建议将其映射到类似于 <Fn>w 的东西上。我个人的 mapleader = F1,所以我使用 <Leader>w

没有问题。无论是否可见,都可以立即输入:w:w!,并且似乎按预期工作。 - Amith
1
缺乏视觉反馈,特别是在预期情况下,是一个巨大的烦恼。我认为这是一个错误。但每个人都有自己的看法。 - usretc

0
所以,这个巧妙的命令:w !sudo tee % 就像是一个小技巧,当你意识到你忘记用超级管理员权限打开Vim时,可以绕过这个问题。
让我们来分解一下:
:w 是Vim的保存命令。就像在普通文本编辑器中按下Ctrl + S一样。
! 就像是召唤大炮一样,它允许你在Vim内部运行一个shell命令。
sudo 是一个神奇的词,它赋予你超级用户权限。它允许你执行需要管理员权限的操作。
tee 就像是一个多任务处理器。它接收输入并将其发送到一个或多个文件,同时也将其输出到屏幕上。
% 是当前文件名的占位符。所以,如果你正在编辑"superimportant.txt",% 就变成了"superimportant.txt"。
将所有这些组合起来,:w !sudo tee % 就相当于说:“嘿,保存这个文件,但要使用超级用户的魔力!”它将你正在处理的内容传递给tee,然后通过sudo将其传递给文件。这样,即使你没有以管理员权限启动Vim,也可以保存对需要管理员权限的文件的更改。
你可以在这里阅读更多关于在获取权限被拒绝时保存编辑的信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接