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} is
                        executed like with ":!{cmd}", any '!' is replaced with
                        the previous command |:!|.

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

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

为了使所有人都有意义, %应该输出文件名(作为tee的参数),但是我找不到关于此行为的帮助参考。

tl; dr有人可以帮我剖析此命令吗?

答案

:w !sudo tee % ...

%表示 “当前文件”

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

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

:w没有更新您的文件

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

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

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

理解三通

对于tee ,在常规 bash 管道情况下,将tee命令描绘为 T 形管道:它将输出定向到指定文件, 还将其发送到标准输出 ,该命令可以由下一个管道命令捕获。

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

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

(使用Asciiflow创建的图。)

有关更多信息,请参见tee手册页

发球台

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

在执行的命令行中, %代表当前文件名 。在: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。

这也很好用:

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

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

注意

"必须使用的,而不是'因为我们想要%到传递到外壳前都会进行扩展。

:w写入文件。

!sudo调用 shell sudo 命令。

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

教程链接

可接受的答案涵盖了所有内容,因此,我仅举一个记录所使用的快捷方式的示例。

将其添加到您的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>是如何在此处编写管道符号以分隔两个命令的方法。

希望能帮助到你。另请参阅其他问题:

我想提出另一种方法来解决“打开文件时忘记写sudo操作” 的问题:

无需permission denied ,而必须输入: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
}

而且效果很好。

这是比vim更以bash中心的方法,因此并不是每个人都喜欢。

当然:

  • 在某些情况下它会失败(当文件所有者不是root但需要sudo ,但是仍然可以编辑该功能)
  • 使用vim只读文件时没有任何意义(就我而言,我将tailcat用于小文件)

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

对于新

由于互动呼叫的问题( 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 密码。