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 有人可以帮我解析这个命令吗?
Many of you have probably seen the command that allows you to write on a file that needs root permission, even when you forgot to open vim with sudo:
:w !sudo tee %
The thing is that I don't get what is exactly happening here.
I have already figured this:w
is for this
*: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 |:!|.
so it passes all the lines as standard input.
The !sudo tee
part calls tee
with administrator privileges.
For all to make sense, the %
should output the filename (as a parameter for tee
), but I can't find references on the help for this behavior.
tl;dr Could someone help me dissect this command?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
发布评论
评论(11)
在执行的命令行中,%
代表当前文件名。这记录在 :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!!
将文件保存为 root。
已接受的答案涵盖了所有内容,因此我仅给出我使用的快捷方式的另一个示例,以供记录。
将其添加到您的 etc/vim/vimrc
(或 ~/.vimrc
):
cnoremap w!!执行'沉默!写 !sudo tee % >/dev/null'
; edit!
其中:
cnoremap
:告诉 vim 将在命令行中关联以下快捷方式。w!!
:快捷方式本身。execute '...'
:执行以下字符串的命令。silent!
:静默运行write !sudo tee % >/dev/null
:OP 问题,添加了将消息重定向到NULL
制作一个干净的命令
:这个技巧是蛋糕中的樱桃:它还调用; edit! edit
命令来重新加载缓冲区,然后避免诸如缓冲区已更改之类的消息。
是如何编写管道符号来分隔两个命令的。
希望有帮助。另请参阅其他问题:
我想建议另一种方法来解决“哦,我在打开文件时忘记写 sudo
” 问题:
而不是收到权限被拒绝
,并且必须输入 :w!!
,我发现使用条件 vim
命令来执行 sudo vim
if fileowner 更优雅是根
。
这很容易实现(甚至可能有更优雅的实现,我显然不是 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
只读文件时(就我而言,我使用tail
或cat
小文件)
但是我发现这带来了更好的开发用户体验,恕我直言,在使用bash
时往往会忘记这一点。 :-)
对于 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 密码。
截至 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 之间进行选择。如果您不在乎,请使用静音版本。
- [O]k - 保留您的撤消历史记录,但会导致您在尝试退出时收到警告。你必须使用 :q!退出。
- [L]oad - 擦除您的撤消历史记录并重置“已修改标志”,让您退出时不会收到保存更改的警告。
上述信息来自大量其他答案和评论,但值得注意的是:
Beco 博士的答案:https://stackoverflow。 com/a/48237738/2927555
idbrii对此的评论:https://stackoverflow.com/a/25010815/2927555< /a>
HanSeoul-Oh 对此的评论:vim“用 sudo 写入”技巧如何工作?
Bruno Bronosky 评论对此: https://serverfault.com/a/22576/195239
这个答案也解释了为什么显然是最简单的方法这不是一个好主意:
https://serverfault.com/a/26334/195239
所以,当你意识到忘记使用全能的 sudo 打开 Vim 时,这个漂亮的命令 :w !sudo tee % 就像一个小解决方法。
让我们分解一下:
:w 是 Vim 表达“保存”的方式。这就像在常规文本编辑器中按 Ctrl + S 一样。
!就像召唤大炮一样 - 它可以让你从 Vim 内部运行 shell 命令。
sudo 是一个神奇的词,它可以赋予您超级用户的权力。它可以让您执行需要管理员权限的操作。
Tee 就像一个多任务处理者。它接受输入并将其发送到一个或多个文件,并将其输出到屏幕上。
% 是当前文件名的占位符。因此,如果您正在编辑“superimportant.txt”,% 将变为“superimportant.txt”。
把它们放在一起, :w !sudo tee % 就像在说,“嘿,保存这个文件,但是用超级用户 mojo 来做!”它将您正在处理的内容交给 tee,然后 tee 使用 sudo 将其传递到文件。这样,您就可以保存对需要管理员权限的文件的更改,即使您没有使用这些权限启动 Vim。
您可以阅读有关在权限被拒绝时保存编辑的更多信息 这里
在 Neovim 中不起作用,因为 shell 存在要求内联密码的问题。在幕后我认识到 C readline() 函数使用转义字符做自己奇怪的事情。
但是,要强制将密码传递到 sudo
中,我们可以将密码输入 STDIN,如下所示:
:w !sudo tee % <<<< mypassword
警告你的密码将会以明文形式写入 vim 历史记录中。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
在
:w !sudo tee %
...%
表示“当前文件”As eugene y 指出,
%
确实意味着“当前文件名”,它被传递给tee
以便它知道要覆盖哪个文件。(在替换命令中,它略有不同;如
:help :%
所示,它等于1,$(整个文件)
(感谢@Orafu指出这不会计算为文件名),例如,:%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
,将tee
命令想象为正常 bash 管道情况下的 T 形管道:它将输出定向到指定的文件和 还将其发送到标准输出,可以由下一个管道命令捕获。例如,在 ps -ax | 中三通进程.txt | grep 'foo',进程列表将被写入一个文本文件并传递给
grep
。(使用 Asciiflow 创建的图表。)
请参阅
tee
手册页 了解更多信息。Tee 作为黑客
在您的问题描述的情况下,使用
tee
是一种黑客,因为我们忽略了它的一半功能。sudo tee
写入我们的文件并将缓冲区内容发送到标准输出,但我们忽略标准输出。在这种情况下,我们不需要将任何内容传递给另一个管道命令;我们只是使用tee
作为编写文件的替代方式,以便我们可以使用sudo
调用它。让这个技巧变得简单
你可以将其添加到
.vimrc
中,使这个技巧变得易于使用:只需输入:w!!
即可。<代码>> /dev/null 部分显式丢弃标准输出,因为正如我所说,我们不需要将任何内容传递给另一个管道命令。
In
:w !sudo tee %
...%
means "the current file"As eugene y pointed out,
%
does indeed mean "the current file name", which is passed totee
so that it knows which file to overwrite.(In substitution commands, it's slightly different; as
:help :%
shows, it'sequal to 1,$ (the entire file)
(thanks to @Orafu for pointing out that this does not evaluate to the filename). For example,:%s/foo/bar
means "in the current file, replace occurrences offoo
withbar
." If you highlight some text before typing:s
, you'll see that the highlighted lines take the place of%
as your substitution range.):w
isn't updating your fileOne confusing part of this trick is that you might think
:w
is modifying your file, but it isn't. If you opened and modifiedfile1.txt
, then ran:w file2.txt
, it would be a "save as";file1.txt
wouldn't be modified, but the current buffer contents would be sent tofile2.txt
.Instead of
file2.txt
, you can substitute a shell command to receive the buffer contents. For instance,:w !cat
will just display the contents.If Vim wasn't run with sudo access, its
:w
can't modify a protected file, but if it passes the buffer contents to the shell, a command in the shell can be run with sudo. In this case, we usetee
.Understanding tee
As for
tee
, picture thetee
command as a T-shaped pipe in a normal bash piping situation: it directs output to specified file(s) and also sends it to standard output, which can be captured by the next piped command.For example, in
ps -ax | tee processes.txt | grep 'foo'
, the list of processes will be written to a text file and passed along togrep
.(Diagram created with Asciiflow.)
See the
tee
man page for more info.Tee as a hack
In the situation your question describes, using
tee
is a hack because we're ignoring half of what it does.sudo tee
writes to our file and also sends the buffer contents to standard output, but we ignore standard output. We don't need to pass anything to another piped command in this case; we're just usingtee
as an alternate way of writing a file and so that we can call it withsudo
.Making this trick easy
You can add this to your
.vimrc
to make this trick easy-to-use: just type:w!!
.The
> /dev/null
part explicitly throws away the standard output, since, as I said, we don't need to pass anything to another piped command.