1597

你们中的许多人可能已经看到允许您在需要 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有人可以帮我剖析这个命令吗?

4

9 回答 9

1814

: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,则可以使用 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是一种黑客行为,因为我们忽略了它的一半功能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部分明确丢弃了标准输出,因为正如我所说,我们不需要将任何内容传递给另一个管道命令。

于 2011-08-16T12:49:09.663 回答
106

在执行的命令行中,%代表当前文件名。这记录在: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是将标准输入复制到一个或多个文件,也复制到标准输出。因此,以root:w !sudo tee % > /dev/null身份有效地将当前缓冲区的内容写入当前文件。可用于此的另一个命令是: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。

于 2010-04-08T14:45:49.440 回答
21

接受的答案涵盖了所有内容,因此我将提供另一个我使用的快捷方式示例,以作记录。

将其添加到您的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>这里是如何写管道符号来分隔两个命令。

希望能帮助到你。另见其他问题:

于 2018-01-13T07:12:59.023 回答
20

:w- 写一个文件。

!sudo- 调用 shell sudo 命令。

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

教程链接

于 2011-06-04T06:02:49.050 回答
20

这也很好用:

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

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

注意

"必须使用而不是'因为我们想%在传递给 shell 之前进行扩展。

于 2014-07-29T08:08:07.833 回答
8

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

我发现有一个条件permission denied命令来执行if文件所有者是.:w!!vimsudo vimroot

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

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

而且效果非常好。

这是一种bashvim-one 更以 -one 为中心的方法,因此不是每个人都喜欢它。

当然:

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

但我发现这带来了更好的开发用户体验,恕我直言,使用时往往会忘记这一点bash。:-)

于 2018-02-28T18:21:56.837 回答
6

为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 密码。

于 2019-10-03T09:07:15.447 回答
1

关于我在 2020 年找到的最常见答案的总结(以及非常小的改进)。

tl;博士

: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 之间进行选择。如果您不在乎,请使用静音版本。

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

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

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

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

Han Seoul-Oh 对此的评论:vim“用 sudo 编写”技巧如何工作?

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

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

于 2020-08-15T07:09:40.367 回答
0

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

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

于 2021-02-10T20:52:31.767 回答