Как работает трюк 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.