Подтвердить что ты не робот

Как работает трюк 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 Может ли кто-нибудь помочь мне проанализировать эту команду?

4b9b3361

Ответ 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.