Как работает трюк vim «напиши с sudo»?

1413

Многие из вас, вероятно, видели команду, которая позволяет вам писать в файле, для которого требуется разрешение 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 Может ли кто-нибудь помочь мне разобрать эту команду?

двойник
источник
5
@ Натан: Не :w !sudo cat > %будет работать, а не загрязнять стандартный вывод?
Бьярке Фрейнд-Хансен
51
@bjarkef - нет, это не работает. В таком случае sudoприменяется к cat, но не к >, поэтому не допускается. Можно попробовать запустить всю команду в подоболочке sudo, например :w !sudo sh -c "cat % > yams.txt", но это тоже не сработает, потому что в подоболочке %- ноль; вы удалите содержимое вашего файла.
Натан Лонг
3
Я просто хочу добавить, что после ввода этой команды может появиться предупреждающее сообщение. Если это так, нажмите L. Затем вам будет предложено нажать Enter. Сделайте, и, наконец, ваш файл будет сохранен.
Паблофиумара
9
@NathanLong @knittl: на :w !sudo sh -c "cat >%"самом деле работает так же хорошо, как и sudo tee %потому, что Vim заменяет имя файла %до того, как оно попадет в подоболочку. Однако ни один из них не работает, если в имени файла есть пробелы; Вы должны сделать :w !sudo sh -c "cat >'%'"или :w !sudo tee "%"исправить это.
Хан Сеул-О,
1
Сохраните с помощью: W и перезагрузите файл: команда W: выполнить ': silent w! Sudo tee%> / dev / null' | :редактировать!
Диего Роберто Дос Сантос

Ответы:

1613

В :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команду как Т-образную трубу в обычной ситуации bash-трубопровода: она направляет вывод в указанный файл (-ы), а также отправляет его на стандартный вывод , который может быть захвачен следующей командой-конвейером.

Например, в ps -ax | tee processes.txt | grep 'foo', список процессов будет записан в текстовый файл и передан в grep.

     +-----------+    tee     +------------+
     |           |  --------  |            |
     | ps -ax    |  --------  | grep 'foo' |
     |           |     ||     |            |
     +-----------+     ||     +------------+
                       ||   
               +---------------+
               |               |
               | processes.txt |
               |               |
               +---------------+

(Диаграмма, созданная с помощью Asciiflow .)

Смотрите teeman-страницу для получения дополнительной информации.

Ти как взломать

В ситуации, описанной вашим вопросом, использование 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часть явно отбрасывает стандартный вывод, поскольку, как я уже сказал, нам не нужно ничего передавать другой переданной команде.

Натан Лонг
источник
147
Особенно нравится ваша запись "W !!" что так легко запомнить после использования "sudo !!" в командной строке.
Эйдан Кейн
11
Так что это использует teeдля его способности писать стандартный ввод в файл. Я удивлен, что нет программы, работа которой заключается в том, чтобы сделать это (я нашел программу, о которой я никогда не слышал, spongeкоторая вызывает это). Я предполагаю, что типичное «записать поток в файл» выполняется встроенной оболочкой. Разве Vim !{cmd}не разветвляется ( cmdвместо этого разветвляется )? Возможно, что-то более очевидное - использовать какой-то рабочий вариант, sh -c ">"а не tee.
Стивен Лу
2
@Steven Lu: spongeявляется частью moreutilsпакета почти во всех дистрибутивах, кроме дистрибутивов на основе Debian. moreutilsимеет довольно хорошие инструменты, которые находятся на одном уровне с более распространенными инструментами, такими как xargsи tee.
Швейцария,
10
Как расширить этот псевдоним, чтобы он также указывал vim автоматически загружать измененное содержимое файла в текущий буфер? Он спрашивает меня об этом, как автоматизировать это?
Златко
10
@ user247077: В этом случае catвыполняется от имени пользователя root, а вывод перенаправляется оболочкой, которая не запускается от имени пользователя root. Это так же, как echo hi > /read/only/file.
WhyNotHugo
99

В выполненной командной строке %обозначает текущее имя файла . Это задокументировано в :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эффективно записывает содержимое текущего буфера в текущий файл , будучи корневым . Еще одна команда, которая может быть использована для этого 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.

Евгений Ярмаш
источник
1
Интересно, :help _%поднимает то, что вы ввели, но :help %поднимает ключ соответствия скобок. Я бы не подумал попробовать префикс подчеркивания, это какой-то шаблон в документации vim? Есть ли какие-то «особые» вещи, которые стоит попробовать при поиске помощи?
Дэвид Папа
2
@David: helpкоманда переходит на тег. Вы можете увидеть доступные теги с :h help-tags. Вы также можете использовать завершение командной строки, чтобы увидеть совпадающие теги: :h cmdline<Ctrl-D>(или, :h cmdline<Tab>если вы установили wildmodeсоответственно)
Евгений Ярмаш
1
Мне пришлось использовать cmap w!! w !sudo tee % > /dev/nullв моем файле .vimrc, чтобы сделать эту работу. Является %неуместны в ответе выше? (Здесь нет эксперта по vim.)
dmmfll
@DMfll Да, это так. Команда в ответе приведет к тому, sudo tee > /dev/null /path/to/current/fileчто на самом деле не имеет смысла. (Собирается редактировать это)
Jazzpi
1
@jazzpi: Ты не прав. Оболочкам на самом деле все равно, где в командной строке вы делаете перенаправление файлов.
Евгений Ярмаш
19

Это также хорошо работает:

:w !sudo sh -c "cat > %"

Это вдохновлено комментарием @Nathan Long.

УВЕДОМЛЕНИЕ :

"должен использоваться вместо того, 'потому что мы хотим %быть расширенными перед передачей в оболочку.

Feihu
источник
11
Хотя это может работать, но также предоставляет sudo доступ к нескольким программам (sh и cat). Другие примеры могут быть более безопасными, если их заменить teeна, /usr/bin/teeчтобы предотвратить атаки с изменением PATH.
ИДБРИ
18

:w - Написать файл.

!sudo - Вызвать команду оболочки sudo.

tee- Вывод команды write (vim: w) перенаправлен с использованием tee. % - это не что иное, как текущее имя файла, т.е. /etc/apache2/conf.d/mediawiki.conf. Другими словами, команда tee запускается от имени пользователя root и принимает стандартный ввод и записывает его в файл, представленный%. Однако, это побудит перезагрузить файл снова (нажмите L, чтобы загрузить изменения в самом vim):

учебная ссылка

кэв
источник
9

Принятый ответ охватывает все, поэтому я приведу еще один пример ярлыка, который я использую для записи.

Добавьте его к своему 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: вопрос ОП, добавлено перенаправление сообщений, NULLчтобы сделать чистую команду
  • <bar> edit!Этот трюк - самая лучшая вещь: он также вызывает editкоманду для перезагрузки буфера, а затем избегает сообщений, таких как изменение буфера . <bar>как написать символ канала для разделения двух команд здесь.

Надеюсь, поможет. Смотрите также для других проблем:

Доктор Беко
источник
молчать! отключил запрос пароля, чтобы вы его не видели
Энди Рэй,
7

Я хотел бы предложить другой подход к проблеме "Упс, которую я забыл написать sudoпри открытии моего файла" :

Вместо того, чтобы получать 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ориентированный подход, чем vimодин, поэтому не всем он может понравиться.

Конечно:

  • есть случаи использования, когда это не удастся (когда владелец файла не root но требует sudo, но функция может быть отредактирована в любом случае)
  • это не имеет смысла при использовании vimфайла только для чтения (насколько я понимаю, я используюtail или catдля небольших файлов)

Но я нахожу, что это дает гораздо лучший опыт для разработчиков , что IMHO, как правило, забывают при использовании bash. :-)

Августин Ридингер
источник
5
Просто знайте, что это намного менее прощающе. Лично большинство моих ошибок - глупые ошибки. Поэтому я предпочитаю получать хедз-ап, когда я делаю что-то, что может быть и глупым, и косвенным. Это, конечно, вопрос предпочтения, но повышение привилегий должно быть добросовестным актом. Кроме того: если вы испытываете это так часто, чтобы сделать ": w !!" достаточно беспокойства, чтобы автоматически sudo (но только если owner = root); Вы можете проверить свой текущий рабочий процесс.
Пейн
Интересный комментарий, хотя нужно помнить, потому что при открытии файла rootзапрашивается пароль.
Августин Ридингер
Ах! Там есть разница Это зависит от того, установлен ли пользователь sudo «NOPASSWD» или нет.
Пейн
Тогда иметь NOPASSWD - это то, что меньше всего прощает ... :)
Августин Ридингер,
4

ДЛЯ НЕОВИМ

Из-за проблем с интерактивными вызовами ( 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.

dbranco
источник