Как работает трюк vim "write with sudo"?
Многие из вас, вероятно, видели команду, которая позволяет писать файл, требующий прав root, даже если вы забыли открыть vim с помощью sudo:
: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 Может ли кто-нибудь помочь мне проанализировать эту команду?
Ответы
Ответ 1
В :w !sudo tee %
...
%
означает "текущий файл"
Как Евгений у указал, %
действительно означает "текущее имя файла", которое передается 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
вы можете подставить команду оболочки для получения содержимого буфера. Например, :w !cat
будет просто отображать содержимое.
Если Vim не запускался с доступом sudo, его :w
не может изменить защищенный файл, но если он передает содержимое буфера в оболочку, команду в оболочке можно запустить с помощью sudo. В этом случае мы используем tee
.
Понимание тройника
Что касается tee
, представьте команду tee
как Т-образную pipeу в нормальной ситуации bash-pipeопровода: она направляет вывод в указанный файл (-ы) и также отправляет его на стандартный вывод, который может быть захваченным следующей переданной по pipeопроводу командой.
Например, в ps -ax | tee processes.txt | grep 'foo'
список процессов будет записан в текстовый файл и передан в grep
.
+-----------+ tee +------------+
| | -------- | |
| ps -ax | -------- | grep 'foo' |
| | || | |
+-----------+ || +------------+
||
+---------------+
| |
| processes.txt |
| |
+---------------+
(диаграмма, созданная с помощью Asciiflow.)
Для получения дополнительной информации см. справочную страницу 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
явно отбрасывает стандартный вывод, поскольку, как я уже сказал, нам не нужно ничего передавать другой переданной команде.
Ответ 2
В выполненной командной строке %
обозначает текущее имя файла. Это описано в :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.
Ответ 3
Это также хорошо работает:
:w !sudo sh -c "cat > %"
Это вдохновляет комментарий @Nathan Long.
УВЕДОМЛЕНИЕ
"
должен использоваться вместо '
, потому что мы хотим, чтобы %
был расширен до передачи в оболочку.
Ответ 4
:w
- Записать файл.
!sudo
- Вызывать команду оболочки sudo.
tee
- вывод команды write (vim: w), перенаправленный с использованием tee. % - не что иное, как текущее имя файла i.e./etc/apache2/conf.d/mediawiki.conf. Другими словами, команда tee запускается как root, и она принимает стандартный ввод и записывает его в файл, представленный%. Однако это приведет к повторному перезагрузке файла (нажмите L, чтобы загрузить изменения в самом vim):
ссылка для учебника
Ответ 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
при открытии моего файла":
Вместо того, чтобы получить permission denied
в permission denied
, и необходимо набрать :w!!
Я считаю более элегантным иметь условную команду vim
которая делает sudo vim
если владельцем файла является root
.
Это так же просто реализовать (могут быть и более элегантные реализации, я явно не баш-гуру):
function vim(){
OWNER=$(stat -c '%U' $1)
if [[ "$OWNER" == "root" ]]; then
sudo /usr/bin/vim $*;
else
/usr/bin/vim $*;
fi
}
И это работает очень хорошо.
Это более подход bash
-centered, чем vim
-one, поэтому не всем это понравится.
Конечно:
- есть случаи, когда это не удастся (когда владелец файла не является пользователем
root
но требует sudo
, но функция может быть отредактирована в любом случае) - это не имеет смысла при использовании
vim
для чтения файла (насколько я понимаю, я использую tail
или cat
для небольших файлов)
Но я нахожу, что это дает гораздо лучший опыт для разработчиков, что IMHO, как правило, забывается при использовании bash
. :-)
Ответ 7
Приведенное выше отображение отлично работает для меня, чтобы сохранить использование sudo и перезагрузить
файл без нажатия дополнительных клавиш. (просто нажмите w)
map w :execute ':silent w !sudo tee % > /dev/null' <bar> :edit! <cr>
Ответ 8
ДЛЯ НЕОВИМ
Из-за проблем с интерактивными вызовами (https://github.com/neovim/neovim/issues/1716) я использую это для neovim, основываясь на ответе доктора Беко:
cnoremap w!! execute 'silent! write !SUDO_ASKPASS='which ssh-askpass' sudo tee % >/dev/null' <bar> edit!
Откроется диалоговое окно, использующее ssh-askpass
и запрашивающее пароль sudo.