你们中的许多人可能已经看到允许您在需要 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 有人可以帮我剖析这个命令吗?
:w !sudo cat > %
不能正常工作,并且不会污染标准输出吗?
sudo
应用于 cat
,但不应用于 >
,因此不允许。您可以尝试在 sudo 子 shell 中运行整个命令,例如 :w !sudo sh -c "cat % > yams.txt"
,但这也不起作用,因为在子 shell 中,%
为 nil;您将清空文件的内容。
:w !sudo sh -c "cat >%"
实际上和 sudo tee %
一样有效,因为 Vim 在 %
到达子shell 之前将其替换为文件名。但是,如果文件名中有空格,它们都不起作用;您必须执行 :w !sudo sh -c "cat >'%'"
或 :w !sudo tee "%"
才能解决此问题。
在 :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
部分显式 丢弃标准输出,因为正如我所说,我们不需要将任何内容传递给另一个管道命令。
在执行的命令行中,%
代表当前文件名。这记录在 :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 文档中的某种模式吗?在寻求帮助时,还有什么其他“特别”的事情可以尝试吗?
help
命令跳转到一个标签。您可以使用 :h help-tags
查看可用标签。您还可以使用命令行完成来查看匹配的标签::h cmdline<Ctrl-D>
(或 :h cmdline<Tab>
,如果您相应地设置 wildmode
)
cmap w!! w !sudo tee % > /dev/null
才能完成这项工作。 %
在上面的答案中是否放错了位置? (这里没有 vim 专家。)
sudo tee > /dev/null /path/to/current/file
这实际上没有意义。 (要编辑那个)
接受的答案涵盖了所有内容,因此我将提供另一个我使用的快捷方式示例,以作记录。
将其添加到您的 etc/vim/vimrc
(或 ~/.vimrc
):
cnoremap w!!执行'沉默!写 !sudo tee % >/dev/null'
在哪里:
cnoremap: 告诉 vim 在命令行中关联下面的快捷方式。
w!!:快捷方式本身。
执行'...':执行以下字符串的命令。
静默!:静默运行
write !sudo tee % >/dev/null: OP 问题,添加了将消息重定向到 NULL 以生成干净的命令
希望能帮助到你。另见其他问题:
超级用户:强制 vim 写入文件
这也很好用:
:w !sudo sh -c "cat > %"
这是受到@Nathan Long 评论的启发。
注意:
必须使用 "
而不是 '
,因为我们希望在传递给 shell 之前扩展 %
。
tee
替换为 /usr/bin/tee
以防止 PATH 修改攻击,其他示例可能会更安全。
:w
- 写入文件。
!sudo
- 调用 shell sudo 命令。
tee
- 使用 tee 重定向的 write (vim :w) 命令的输出。 % 只不过是当前文件名,即 /etc/apache2/conf.d/mediawiki.conf。换句话说,tee 命令以 root 身份运行,它接受标准输入并将其写入由 % 表示的文件。但是,这将提示重新加载文件(按 L 以加载 vim 本身的更改):
对于 “打开文件时我忘记写 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
时往往会忘记这一点。 :-)
root
的形式打开时,会查询密码。
为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 密码。
关于我在 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
cnoremap w!!
的唯一问题是,每当您在 :
命令提示符下键入 w!
时,它都会将 w
替换为 !
(并挂起直到您键入下一个字符)。就像你想用 w!
实际强制保存一样。此外,即使它不是 :
之后的第一件事。
因此,我建议将其映射到 <Fn>w
之类的东西。我个人有 mapleader = F1,所以我使用 <Leader>w
。
:w
和 :w!
无需等待即可输入,即使在屏幕上看不到,也能正常工作。
不定期副业成功案例分享
tee
将标准输入写入文件的能力。我很惊讶没有一个程序可以做到这一点(我发现一个我从未听说过的名为sponge
的程序可以做到这一点)。我猜典型的“将流写入文件”是由内置的 shell 执行的。 Vim 的!{cmd}
不分叉一个 shell(而是分叉cmd
)吗?也许更明显的事情是使用sh -c ">"
而不是tee
的一些工作变体。sponge
是几乎所有发行版中moreutils
软件包的一部分,除了基于 Debian 的发行版。moreutils
有一些相当不错的工具,与更常见的工具(如xargs
和tee
)相当。cat
以 root 身份运行,并且输出由不以 root 身份运行的 shell 重定向。与echo hi > /read/only/file
相同。