【问题标题】:How does the vim "write with sudo" trick work?vim“用 sudo 编写”技巧是如何工作的?
【发布时间】:2011-02-05 17:53:21
【问题描述】:

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

【问题讨论】:

  • @Nathan::w !sudo cat > % 会不会工作得那么好,不会污染标准输出?
  • @bjarkef - 不,这不起作用。在这种情况下,sudo 应用于cat,但不应用于>,因此不允许。您可以尝试在 sudo 子shell 中运行整个命令,例如:w !sudo sh -c "cat % > yams.txt",但这也不起作用,因为在子shell 中,% 为零;您将清空文件的内容。
  • 我只想补充一点,在输入该命令后,可能会出现一条警告消息。如果是这样,请按 L。然后,您将被要求按 Enter。这样做,您最终将保存文件。
  • @NathanLong @knittl: :w !sudo sh -c "cat >%" 实际上和sudo tee % 一样有效,因为Vim 在到达子shell 之前将文件名替换为%。但是,如果文件名中有空格,它们都不起作用;你必须做:w !sudo sh -c "cat >'%'":w !sudo tee "%" 来解决这个问题。
  • 使用 :W 保存并重新加载文件: command W :execute ':silent w !sudo tee % > /dev/null' | :编辑!

标签: vim sudo


【解决方案1】:

在执行的命令行中,% 代表当前文件名。这记录在: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 有效地将当前缓冲区的内容写入当前文件作为根。另一个可用的命令是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。

【讨论】:

  • 有趣,:help _% 会显示您输入的内容,但:help % 会显示大括号匹配键。我不会考虑尝试下划线前缀,这是 vim 文档中的某种模式吗?在寻求帮助时,还有什么其他“特别”的事情可以尝试吗?
  • @David:help 命令跳转到一个标签。您可以使用:h help-tags 查看可用标签。您还可以使用命令行完成来查看匹配的标签::h cmdline<Ctrl-D>(或 :h cmdline<Tab>,如果您相应地设置了 wildmode
  • 我必须在我的 .vimrc 文件中使用 cmap w!! w !sudo tee % > /dev/null 才能完成这项工作。 % 是否在上面的答案中放错了位置? (这里没有 vim 专家。)
  • @DMfll 是的。答案中的命令将导致 sudo tee > /dev/null /path/to/current/file 这实际上没有意义。 (要编辑)
  • @jazzpi:你错了。 Shells 实际上并不关心您在命令行的哪个位置进行文件重定向。
【解决方案2】:

:w - 写一个文件。

!sudo - 调用 shell sudo 命令。

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

tutorial link

【讨论】:

    【解决方案3】:

    :w !sudo tee %...

    % 表示“当前文件”

    作为eugene y pointed out% 确实表示“当前文件名”,它被传递给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 中的命令可以 em> 使用 sudo 运行。在这种情况下,我们使用tee

    了解三通

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

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

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

    (使用Asciiflow 创建的图表。)

    请参阅tee man page 了解更多信息。

    Tee 作为一个黑客

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

    【讨论】:

    • 特别喜欢你的符号“w!!”使用“sudo !!”后很容易记住在命令行上。
    • 所以它使用tee 来将标准输入写入文件。我很惊讶没有一个程序可以做到这一点(我发现了一个我从未听说过的名为sponge 的程序可以做到这一点)。我猜典型的“将流写入文件”是由内置的 shell 执行的。 Vim 的 !{cmd} 不分叉一个外壳(而不是分叉 cmd)吗?也许更明显的事情是使用sh -c ">" 的一些工作变体而不是tee
    • @Steven Lu:sponge 是几乎所有发行版中moreutils 软件包的一部分,除了基于 Debian 的发行版。 moreutils 有一些相当不错的工具,与更常见的工具(如 xargstee)相当。
    • 如何扩展这个别名来告诉vim自动加载改变的文件内容到当前缓冲区?它问我,如何自动化?
    • @user247077:在这种情况下,cat 以 root 身份运行,并且输出由不以 root 身份运行的 shell 重定向。和echo hi > /read/only/file一样。
    【解决方案4】:

    这也很好用:

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

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

    注意

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

    【讨论】:

    • 虽然这可能有效,但它也让 sudo 可以访问多个程序(sh 和 cat)。通过将tee 替换为/usr/bin/tee 来防止路径修改攻击,其他示例可能会更安全。
    【解决方案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> 是这里如何写管道符号来分隔两个命令。

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

    【讨论】:

    • 静默!禁用密码提示,因此您看不到它
    【解决方案6】:

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

    如果文件所有者是root,我发现有条件vim 命令执行sudo vim,而不是接收permission denied,并且必须输入:w!!

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

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

    而且效果非常好。

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

    当然:

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

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

    【讨论】:

    • 请注意,这不那么宽容。就个人而言,我的大多数错误都是愚蠢的错误。因此,当我在做一些既愚蠢又重要的事情时,我更愿意提醒自己。这当然是一个偏好问题,但特权升级应该是一种认真的行为。另外:如果您经常遇到这种情况以致于制作 ":w!!"很麻烦地默默地自动 sudo(但前提是 owner=root);您可能需要检查您当前的工作流程。
    • 有趣的评论,但应该注意,因为当文件以root 打开时,会查询密码。
    • 啊!有区别。这取决于 sudo 用户是否设置了“NOPASSWD”。
    • 然后拥有 NOPASSWD 什么是不那么宽容... :)
    【解决方案7】:

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

    【讨论】:

      【解决方案8】:

      我在 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

      韩首尔-Oh对此的评论:How does the vim "write with sudo" trick work?

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

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

      【讨论】:

        【解决方案9】:

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

        因此,我建议将其映射到 &lt;Fn&gt;w 之类的东西。我个人有 mapleader = F1,所以我使用的是&lt;Leader&gt;w

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-02-14
          • 2012-05-10
          • 1970-01-01
          • 2013-04-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多