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

时间:2010-04-08 14:36:51

标签: 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 有人可以帮助我剖析这个命令吗?

9 个答案:

答案 0 :(得分:1444)

:w !sudo tee % ...

%表示“当前文件”

作为eugene y pointed out%确实意味着“当前文件名”。 Vim中的另一个用途是替换命令。例如,:%s/foo/bar表示“当前文件中的 ,将foo替换为bar。”如果您在键入:s之前突出显示某些文字,则会看到突出显示的行代替%替换范围。

:w未更新您的文件

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

您可以替换shell命令来接收缓冲区内容,而不是file2.txt。例如,: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 page

Tee as a hack

在您的问题描述的情况下,使用tee是一种黑客攻击,因为我们忽略了它的一半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部分显式会抛弃标准输出,因为正如我所说,我们不需要将任何内容传递给另一个管道命令。

答案 1 :(得分:93)

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

答案 2 :(得分:18)

这也很有效:

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

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

<强>注意

必须使用

"代替',因为我们希望在传递给shell之前展开%

答案 3 :(得分:16)

:w - 写一个文件。

!sudo - 调用shell sudo命令。

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

tutorial link

答案 4 :(得分:5)

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

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

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

答案 5 :(得分:3)

我想建议另一种方法“Oups我在打开文件时忘了写sudo问题:

我没有收到permission denied,而是必须输入:w!!,我发现如果文件所有者为{vim条件sudo vim,则会更优雅{1}}。

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

root

它的效果非常好。

这是一种更function vim(){ OWNER=$(stat -c '%U' $1) if [[ "$OWNER" == "root" ]]; then sudo /usr/bin/vim $*; else /usr/bin/vim $*; fi } 为中心的方法而不是bash - 因此不是每个人都喜欢它。

当然:

  • 有些用例会失败(文件所有者不是vim但需要root,但无论如何都可以编辑该功能)
  • 使用sudo进行只读文件时没有任何意义(就我而言,我使用vimtail作为小文件)

但我发现这会带来更好的开发用户体验,这是恕我直言,在使用cat时会被遗忘的事情。 : - )

答案 6 :(得分:2)

对于NEOVIM

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

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

这将打开一个使用ssh-askpass的对话框,要求输入sudo密码。

答案 7 :(得分:2)

我在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 tee主题的多个建议中汇总而来的,因此,在我发现的最常见答案上有非常小的改进。我上面的版本:

  • 在文件名中使用空格

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

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

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

    • [确定] -保留撤消历史记录,但是当您尝试退出时会引起警告。您必须使用:q!退出。
    • [L] oad -清除撤消历史记录并重置“修改后的标志”,使您可以退出而不会被警告保存更改。

以上信息是从其他答案和评论中得出的,但值得注意的是:

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

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

韩首尔·汉对此评论:How does the vim "write with sudo" trick work?

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

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

答案 8 :(得分:0)

cnoremap w!! 的唯一问题是,每当您在 w 处输入 ! }} 命令提示符。就像您想实际使用 w! 强制保存时一样。此外,即使它不是 : 之后的第一件事。

因此,我建议将其映射到 w! 之类的内容。我个人有 mapleader = F1,所以我使用 :