ChatGPT解决这个技术问题 Extra ChatGPT

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

@Nathan::w !sudo cat > % 不能正常工作,并且不会污染标准输出吗?
@bjarkef - 不,那行不通。在这种情况下,sudo 应用于 cat,但不应用于 >,因此不允许。您可以尝试在 sudo 子 shell 中运行整个命令,例如 :w !sudo sh -c "cat % > yams.txt",但这也不起作用,因为在子 shell 中,% 为 nil;您将清空文件的内容。
我只想补充一点,在键入该命令后,可能会出现一条警告消息。如果是这样,请按 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' | :编辑!

N
Nathan Long

: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 中的命令可以使用 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

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 部分显式 丢弃标准输出,因为正如我所说,我们不需要将任何内容传递给另一个管道命令。


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

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


有趣的是,: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 实际上并不关心您在命令行的哪个位置进行文件重定向。
D
DrBeco

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

将其添加到您的 etc/vim/vimrc(或 ~/.vimrc):

cnoremap w!!执行'沉默!写 !sudo tee % >/dev/null' 编辑!

在哪里:

cnoremap: 告诉 vim 在命令行中关联下面的快捷方式。

w!!:快捷方式本身。

执行'...':执行以下字符串的命令。

静默!:静默运行

write !sudo tee % >/dev/null: OP 问题,添加了将消息重定向到 NULL 以生成干净的命令

edit!:这个技巧是锦上添花:它还调用 edit 命令重新加载缓冲区,然后避免出现缓冲区已更改等消息。 是这里如何写管道符号来分隔两个命令。

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

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


沉默的!禁用密码提示,因此您看不到它
f
feihu

这也很好用:

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

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

注意:

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


虽然这可能有效,但它还允许 sudo 访问多个程序(sh 和 cat)。通过将 tee 替换为 /usr/bin/tee 以防止 PATH 修改攻击,其他示例可能会更安全。
k
kev

:w - 写入文件。

!sudo - 调用 shell sudo 命令。

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

tutorial link


A
Augustin Riedinger

对于 “打开文件时我忘记写 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 更以 bash 为中心的方法,因此不是每个人都可能喜欢它。

当然:

有些用例会失败(当文件所有者不是 root 但需要 sudo,但无论如何都可以编辑该函数)

使用 vim 只读文件时没有意义(就我而言,我使用 tail 或 cat 处理小文件)

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


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

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


我很欣赏新病毒的考虑。无论出于何种原因,这对我不起作用。我确实找到了解决问题的插件。这是一个相对不平凡的成就,并有一个不错的最终结果。 github.com/lambdalisue/suda.vim - E
C
Chris

关于我在 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 - 删除您的撤消历史记录并重置“修改标志”,允许您退出而不被警告保存更改。

[O]k - 保留您的撤消历史记录,但会导致您在尝试退出时收到警告。你必须使用 :q!退出。

[L]oad - 删除您的撤消历史记录并重置“修改标志”,允许您退出而不被警告保存更改。

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

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

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

Han Seoul-Oh 对此的评论:How does the vim "write with sudo" trick work?

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

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


u
usretc

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

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


在这里看不出问题。 :w:w! 无需等待即可输入,即使在屏幕上看不到,也能正常工作。
缺乏视觉反馈,尤其是在预期的时候,是一个很大的烦恼。我认为这是一个错误。但每一个他自己。