你们中的许多人可能都见过这样一个命令,它允许您在需要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;有人能帮我分析一下这个命令吗?


在执行的命令行中,%代表当前文件名。这在: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!sudotee%>/dev/null有效地将当前缓冲区的内容写入当前文件,同时作为根文件。另一个可用于此操作的命令是dd:

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

作为快捷方式,您可以将此映射添加到.virc中:

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

使用以上选项,您可以键入:w<输入>将文件保存为根目录。


:w-写入文件。

!sudo-调用shell sudo命令。

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

教程链接


在:w!sudo tee%。。。

%表示“当前文件”

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

(在替换命令中,它稍有不同;例如:help:%shows,它等于1,$(整个文件)(感谢@Orafu指出这不符合文件名)。例如,:%s/foo/bar表示“在当前文件中,用bar替换foo的出现。”如果在键入:s之前高亮显示一些文本,您将看到高亮显示的行将代替%作为替换范围。)

:w未更新您的文件

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

您可以用shell命令代替file2.txt来接收缓冲区内容。例如:w!cat将只显示内容。

如果Vim不是使用sudo访问运行的,它的:w不能修改受保护的文件,但是如果它将缓冲区内容传递给shell,则可以使用sudo运行shell中的命令。在本例中,我们使用tee。

理解三通

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

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

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

(使用Asciiflow创建的图表。)

有关更多信息,请参阅tee手册页面。

作为黑客的T恤

在你的问题描述的情况下,使用tee是一种黑客,因为我们忽略了它的一半功能。sudotee写入文件并将缓冲区内容发送到标准输出,但我们忽略标准输出。在这种情况下,我们不需要将任何内容传递给另一个管道命令;我们只是使用tee作为编写文件的另一种方式,这样我们就可以用sudo调用它。

让这个技巧变得简单

您可以将此添加到.virc中,使此技巧易于使用:只需键入: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!!执行'沉默!写sudo tee%>/dev/null'<bar>edit!

哪里:

cnoremap:告诉vim以下快捷方式将在命令行中关联。w!!:快捷方式本身。执行“…”:执行以下字符串的命令。无声!:安静地运行写sudo tee%>/dev/null:OP问题,添加了将消息重定向为null以生成干净的命令<bar>编辑!:这个技巧是小菜一碟:它还调用edit命令来重新加载缓冲区,然后避免缓冲区已更改等消息<bar>是如何在这里编写管道符号来分隔两个命令。

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

超级用户:强制vim写入文件


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

而不是接收拒绝的权限,并且必须键入:w!!,我发现如果文件所有者是root,那么使用一个条件vim命令来执行sudovim会更优雅。

这同样容易实现(甚至可能有更优雅的实现,我显然不是bash大师):

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

而且效果非常好。

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

当然:

在某些用例中,它会失败(当文件所有者不是root用户,但需要sudo,但无论如何都可以编辑该函数)当使用vim只读取文件时(就我而言,我对小文件使用tail或cat),这是没有意义的

但我发现这带来了更好的开发用户体验,这是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;博士

呼叫:w!!或:W!!。展开后,按enter键。

如果你打字太慢!!在w/w之后,它将不会展开,并可能报告:E492:不是编辑器命令:w!!

注意:如果情况不同,请使用哪个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 tee主题的多个建议中总结出来的,因此在我找到的最常见的答案上略有改进。我的以上版本:

在文件名中使用空格通过指定tee的完整路径来减轻路径修改攻击。给你两个映射,W!!无声处决,还有!!表示不沉默,即健谈:-)使用非静默版本的区别在于,您可以在[O]k和[L]load之间进行选择。如果您不介意,请使用静音版本。[O] k-保留撤消历史记录,但在尝试退出时会导致警告。你必须使用:q!退出。[五十] 加载-删除撤消历史记录并重置“已修改标志”,允许您退出而不被警告保存更改。

上述信息来源于一系列其他答案和评论,但值得注意的是:

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

idbrii对此的评论:https://stackoverflow.com/a/25010815/2927555

韩首尔Oh对此的评论是:vim“用sudo写作”技巧是如何发挥作用的?

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

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


cnoremap w的唯一问题!!就是它用!(并挂起,直到键入下一个字符)!在:命令提示下。就像你想用w!强制保存一样!。此外,即使这不是:之后的第一件事。

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