vim“用sudo写字”技巧是如何工作的?

你们中的许多人可能已经看到了允许您在需要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} isexecuted like with ":!{cmd}", any '!' is replaced withthe previous command |:!|.

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

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

为了让所有人都有意义,%应该输出文件名(作为tee的参数),但我找不到对此行为的帮助的引用。

tl; dr有人能帮我剖析这个命令吗?

484935 次浏览

在执行的命令行中,%代表当前文件名。这记录在#1中:

In Ex commands, at places where a file name can be used, the followingcharacters have a special meaning.%       Is replaced with the current file name.

正如您已经发现的那样,:w !cmd将当前缓冲区的内容通过管道传输到另一个命令。#1所做的是将标准输入复制到一个或多个文件,以及标准输出。因此,:w !sudo tee % > /dev/null有效地将当前缓冲区的内容写入当前文件虽然是根。另一个可以用于此的命令是#3

:w !sudo dd of=% > /dev/null

作为快捷方式,您可以将此映射添加到您的.vimrc

" Force saving files that require root permissioncnoremap w!! w !sudo tee > /dev/null %

使用上述命令,您可以键入:w!!<Enter>将文件保存为root。

:w-写一个文件。

!sudo-调用shell sudo命令。

tee-写(vim: w)命令的输出使用tec重定向。%只是当前文件名,即 /etc/apache2/conf.d/mediawiki.conf.换句话说,tec命令以root身份运行,它接受标准输入并将其写入%表示的文件。然而,这将提示再次重新加载文件(点击L加载vim本身的更改):

教程链接

:w !sudo tee %

%表示“当前文件”

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

(在替换命令中,它略有不同;正如:help :%所示,它是equal to 1,$ (the entire file)(感谢@Orafu指出这不会计算文件名)。例如,:%s/foo/bar表示“在当前文件,将foo的出现替换为bar。”如果您在键入:s之前突出显示一些文本,您会看到突出显示的行取代了%作为您的替换范围。)

:w没有更新你的文件

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

而不是file2.txt,您可以替换shell命令以接收缓冲区内容。例如,:w !cat将只显示内容。

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

理解T恤

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

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

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

(用Asciiflow创建的图表。)

查看#0手册页了解更多信息。

三通作为一个黑客

在你的问题描述的情况下,使用#0是一种黑客行为,因为我们忽略了它所做的一半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部分明确丢弃了标准输出,因为正如我所说,我们不需要将任何内容传递给另一个管道命令。

这也很有效:

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

这是由@Nathan Long的评论启发的。

通知

必须使用"而不是',因为我们希望在传递给shell之前扩展%

接受的答案涵盖了所有内容,所以我将再举一个我使用的快捷方式的例子,以供记录。

将其添加到您的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命令来重新加载缓冲区,然后避免像缓冲区已更改这样的消息。

希望有帮助。另见其他问题:

我想建议另一种方法来解决“我在打开文件时忘记写#0”问题:

与其接收permission denied,不得不输入:w!!,我发现如果文件所有者是root,则使用条件vim命令执行sudo vim会更优雅。

这很容易实现(甚至可能有更优雅的实现,我显然不是bash-guru):

function vim(){OWNER=$(stat -c '%U' $1)if [[ "$OWNER" == "root" ]]; thensudo /usr/bin/vim $*;else/usr/bin/vim $*;fi}

而且效果非常好。

这是一个比vim更以bash为中心的方法,所以不是每个人都喜欢它。

当然:

  • 有一些用例会失败(当文件所有者不是root但需要sudo时,但函数无论如何都可以编辑)
  • 使用vim只读文件时没有意义(就我而言,我对小文件使用tailcat

但我发现这带来了更好的Dev用户体验,这是IMHO在使用bash时往往被遗忘的东西。:-)

为NEOVIM

由于交互式呼叫的问题(https://github.com/neovim/neovim/issues/1716),我根据Beco博士的回答将其用于neovim:

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

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

截至2020年,我发现的最常见答案的摘要(和非常小的改进)。

tl; dr

调用: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://stackoverflow.com/a/12870763/2927555

我上面的答案是从关于传统sudo T恤主题的多个建议中得出的,因此在我找到的最常见的答案上略有改进。我上面的版本:

  • 在文件名中使用空格

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

  • 给你两个映射,W!!用于沉默执行,w!!用于不沉默,即健谈:-)

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

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

上述信息来自于许多其他答案和评论,但值得注意的是:

贝科博士的回答:https://stackoverflow.com/a/48237738/2927555

伊布里对此的评论:https://stackoverflow.com/a/25010815/2927555

韩首尔-吴对此的评论:vim“用sudo写字”技巧是如何工作的?

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

这个答案也解释了为什么看似最简单的方法不是一个好主意:https://serverfault.com/a/26334/195239

cnoremap w!!的唯一问题是,每当您在:命令提示符下键入w!时,它会将w替换为!(并挂起直到您键入下一个字符)。就像您想实际使用w!强制保存一样。此外,即使它不是:之后的第一件事。

因此,我建议将它映射到类似于<Fn>w的东西。我个人有mapleader=F1,所以我使用<Leader>w