Як працює трюк vim "писати судо"?


1410

Багато хто з вас, напевно, бачили команду, яка дозволяє писати у файл, який потребує кореневого дозволу, навіть коли ви забули відкрити 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
@Nathan: Не :w !sudo cat > %вдалося б працювати і не забруднити стандартний вихід?
Bjarke Freund-Hansen

51
@bjarkef - ні, це не працює. У цьому випадку sudoзастосовується до cat, але не до >, тому це не дозволено. Ви можете спробувати запустити цілу команду в подобові sudo, як :w !sudo sh -c "cat % > yams.txt", але це також не буде працювати, тому що в нижній частині %- нуль; ви очистите вміст свого файлу.
Натан Лонг

3
Я просто хочу додати, що після введення цієї команди може з’явитися попереджувальне повідомлення. Якщо так, натисніть L. Потім вам буде запропоновано натиснути клавішу Enter. Зробіть, і нарешті ваш файл буде збережено.
pablofiumara

9
@NathanLong @knittl: :w !sudo sh -c "cat >%"насправді працює так само добре, як sudo tee %Vim підміняє ім'я файлу, %перш ніж він коли-небудь потрапить до передплати. Однак жоден з них не працює, якщо ім'я файлу має пробіли; ви повинні це зробити :w !sudo sh -c "cat >'%'"або :w !sudo tee "%"виправити.
Хань Сеул-О

1
Збережіть, використовуючи: W і перезавантажте файл: команда W: Execute ': silent w! Sudo tee%> / dev / null' | : редагувати!
Дієго Роберто Дос Сантос

Відповіді:


1610

В :w !sudo tee %...

% означає "поточний файл"

Як зазначав Євген y , %насправді означає "поточне ім'я файлу", яке передається 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команду як Т-подібну трубу в звичайній ситуації з базуванням трубопроводів: вона направляє вихід на вказаний файл (и), а також надсилає його на стандартний вихід , який може бути захоплений наступною командною командою.

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


147
Особливо подобається ваше позначення "w !!" який так легко запам'ятовується після використання "судо !!" в командному рядку.
Айдан Кейн

11
Таким чином, це використовує teeдля своєї здатності писати stdin у файл. Я здивований, що не існує програми, завдання якої зробити це (я знайшов програму, про яку я ніколи не чув, щоб її називали 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ефективно записує вміст поточного буфера в поточний файл, одночасно використовуючи 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.


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що насправді немає сенсу. (Йдемо це редагувати)
джазпі

1
@jazzpi: Ви помиляєтесь. Оболонки насправді не цікавить, де в командному рядку ви робите переадресацію файлу.
Євген Ярмаш

19

Це також добре працює:

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

На це надихає коментар @Nathan Long.

УВАГА :

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


11
Хоча це може працювати, воно також надає доступ sudo до декількох програм (sh та cat). Інші приклади можуть бути більш безпечним шляхом заміни teeз /usr/bin/teeзапобігти PATH-модифікацію атаки.
idbrii

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-one, тому не всім це може подобатися.

Звичайно:

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

Але я вважаю, що це приносить набагато кращий досвід користувачів розробників , що IMHO, як правило, забувається при використанні bash. :-)


5
Просто пам’ятайте, що це набагато менше прощати. Особисто більшість моїх помилок - це дурні помилки. Тому я вважаю за краще підняти голову, коли роблю щось, що може бути і дурним, і наслідком. Це, звичайно, питання переваги, але ескалація привілеїв повинна була бути добросовісною дією. Також: Якщо ви відчуваєте це так часто, щоб зробити ": w !!" достатньо клопоту, щоб мовчки авто судо (але тільки якщо власник = root); ви можете вивчити поточний робочий процес.
Пейн

Цікавий коментар, хоча слід пам’ятати, оскільки при відкритті файлу при rootзапиті пароля.
Августин Рідінгер

Ах! Є різниця. Це залежить від того, чи користувач sudo встановив "NOPASSWD" чи ні.
Payne

Тоді мати NOPASSWD - це те, що менше прощає ... :)
Августин Ріддінгер

4

ДЛЯ НЕОВІМ

Через проблеми з інтерактивними дзвінками ( https://github.com/neovim/neovim/isissue/1716 ), я використовую це для neovim, грунтуючись на відповіді доктора Беко:

cnoremap w!! execute 'silent! write !SUDO_ASKPASS=`which ssh-askpass` sudo tee % >/dev/null' <bar> edit!

Це відкриє діалогове вікно з використанням ssh-askpassзапиту пароля sudo.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.