Jak działa sztuczka vima „pisz z sudo”?

1410

Wielu z was prawdopodobnie widziało polecenie, które pozwala pisać na pliku, który wymaga uprawnień roota, nawet jeśli zapomniałeś otworzyć vima za pomocą sudo:

:w !sudo tee %

Chodzi o to, że nie rozumiem, co dokładnie się tutaj dzieje.

Już to wymyśliłem: wjest po to

                                                        *: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 |:!|.

więc przekazuje wszystkie linie jako standardowe wejście.

!sudo teeCzęść połączeń teez uprawnieniami administratora.

Aby wszystko miało sens, %powinna wypisać nazwę pliku (jako parametr dla tee), ale nie mogę znaleźć referencji w pomocy dla tego zachowania.

tl; dr Czy ktoś mógłby mi pomóc w rozwikłaniu tego polecenia?

Doppelganger
źródło
5
@Nathan: :w !sudo cat > %Nie działałby tak dobrze i nie zanieczyszczałby standardowego wyjścia?
Bjarke Freund-Hansen
51
@bjarkef - nie, to nie działa. W takim przypadku sudoma zastosowanie do cat, ale nie do >, więc nie jest dozwolone. Możesz spróbować uruchomić całe polecenie w podpowłoce sudo, na przykład :w !sudo sh -c "cat % > yams.txt", ale to też nie zadziała, ponieważ w podpowłoce %jest zero; wyczyścisz zawartość swojego pliku.
Nathan Long
3
Chciałbym tylko dodać, że po wpisaniu tego polecenia może pojawić się komunikat ostrzegawczy. Jeśli tak, naciśnij L. Następnie zostaniesz poproszony o naciśnięcie enter. Zrób, a w końcu twój plik zostanie zapisany.
pablofiumara
9
@NathanLong @knittl: :w !sudo sh -c "cat >%"faktycznie działa tak samo dobrze, sudo tee %ponieważ Vim zastępuje nazwę pliku, %zanim dotrze do podpowłoki. Jednak żadne z nich nie działa, jeśli nazwa pliku zawiera spacje; musisz to zrobić :w !sudo sh -c "cat >'%'"lub :w !sudo tee "%"to naprawić.
Han Seoul-Oh
1
Zapisz używając: W i ponownie załaduj plik: polecenie W: wykonaj ': silent w! Sudo tee%> / dev / null' | :edytować!
Diego Roberto Dos Santos

Odpowiedzi:

1610

W :w !sudo tee %...

% oznacza „bieżący plik”

Jak wskazał eugene y , %rzeczywiście oznacza „bieżącą nazwę pliku”, która jest przekazywana, aby teewiedział, który plik należy zastąpić.

(W poleceniach podmiany, to nieco inaczej, jak :help :%pokazy, to equal to 1,$ (the entire file)(dzięki @Orafu za wskazanie, że to nie ocenia do nazwy pliku) Np. :%s/foo/barŚrodki „ w bieżącym pliku , wymień wystąpienia fooz bar.” Jeśli kulminacyjnym trochę tekstu przed wpisaniem :s, zobaczysz, że podświetlone linie zajmują miejsce %jako zakres podstawiania).

:w nie aktualizuje twojego pliku

Jedną z niejasnych części tej sztuczki jest to, że możesz :wmodyfikować plik, ale tak nie jest. Jeśli otworzyłeś i zmodyfikowałeś file1.txt, a następnie uruchomiłeś :w file2.txt, byłoby to „zapisz jako”; file1.txtnie zostanie zmodyfikowany, ale bieżąca zawartość bufora zostanie wysłana do file2.txt.

Zamiast tego file2.txtmożesz zastąpić polecenie powłoki, aby otrzymać zawartość bufora . Na przykład :w !catwyświetli tylko zawartość.

Jeśli Vim nie był uruchamiany z dostępem sudo, :wnie może zmodyfikować chronionego pliku, ale jeśli przekazuje zawartość bufora do powłoki, polecenie w powłoce można uruchomić za pomocą sudo . W tym przypadku korzystamy tee.

Zrozumienie tee

Co do tego tee, wyobraź sobie teepolecenie jako potok w kształcie litery T w normalnej sytuacji, w której wykonywany jest bash: kieruje wyjście do określonych plików, a także wysyła je do standardowego wyjścia , które może zostać przechwycone przez następne polecenie potoku.

Na przykład ps -ax | tee processes.txt | grep 'foo'lista procesów zostanie zapisana w pliku tekstowym i przekazana dalej grep.

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

(Schemat utworzony za pomocą Asciiflow .)

Zobacz teestronę podręcznika, aby uzyskać więcej informacji.

Tee jako hack

W sytuacji opisanej w pytaniu używanie teeto hack, ponieważ ignorujemy połowę tego, co robi . sudo teezapisuje do naszego pliku, a także wysyła zawartość bufora na standardowe wyjście, ale ignorujemy standardowe wyjście . W tym przypadku nie musimy przekazywać niczego do innego polecenia potokowego; używamy po prostu teejako alternatywnego sposobu zapisu pliku, abyśmy mogli go wywołać sudo.

Ułatwienie tej sztuczki

Możesz dodać to do swojego, .vimrcaby ta sztuczka była łatwa w użyciu: po prostu wpisz :w!!.

" Allow saving of files as sudo when I forgot to start vim using sudo.
cmap w!! w !sudo tee > /dev/null %

> /dev/nullCzęść wyraźnie odrzuca standardowe wyjście, ponieważ, jak powiedziałem, nie musimy przekazać cokolwiek innego polecenia rurami.

Nathan Long
źródło
147
Szczególnie jak twoja notacja „w !!” który jest tak łatwy do zapamiętania po użyciu „sudo !!” w wierszu poleceń.
Aidan Kane,
11
Wykorzystuje to teejego zdolność do zapisywania standardowego wejścia do pliku. Dziwię się, że nie ma programu, którego zadaniem jest to zrobić (znalazłem program, o którym nigdy nie słyszałem, że się nazywa sponge) Myślę, że typowe „zapisz strumień do pliku” jest wykonywane przez wbudowaną powłokę. Czy Vim !{cmd}nie rozwidla muszli ( cmdzamiast tego rozwidla )? Być może czymś bardziej oczywistym byłoby użycie jakiegoś działającego wariantu sh -c ">"zamiast tee.
Steven Lu
2
@Steven Lu: spongejest częścią moreutilspakietu w prawie każdej dystrybucji oprócz dystrybucji opartych na Debianie. moreutilsma całkiem niezłe narzędzia, które są na równi z bardziej popularnymi narzędziami, takimi jak xargsi tee.
Szwajcar
10
Jak rozwinąć ten alias, aby również powiedzieć vimowi, aby automatycznie ładował zmienioną zawartość pliku do bieżącego bufora? Pyta mnie o to, jak to zautomatyzować?
Zlatko
10
@ user247077: W takim przypadku catdziała jako root, a dane wyjściowe są przekierowywane przez powłokę, która nie działa jako root. To jest tak samo jak echo hi > /read/only/file.
WhyNotHugo
99

W wykonanym wierszu poleceń %oznacza bieżącą nazwę pliku . Jest to udokumentowane w :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.

Jak już się dowiedziałeś, :w !cmdpotokuje zawartość bieżącego bufora do innego polecenia. Co teeto jest kopiowanie standardowego wejścia do jednego lub więcej plików, a także na standardowe wyjście. Dlatego :w !sudo tee % > /dev/nullskutecznie zapisuje zawartość bieżącego bufora do bieżącego pliku , będąc rootem . Innym poleceniem, które można w tym celu zastosować, jest dd:

:w !sudo dd of=% > /dev/null

Jako skrót możesz dodać to mapowanie do .vimrc:

" Force saving files that require root permission 
cnoremap w!! w !sudo tee > /dev/null %

Za pomocą powyższego możesz wpisać, :w!!<Enter>aby zapisać plik jako root.

Eugene Yarmash
źródło
1
Interesujące, :help _%wyświetla to, co wpisałeś, ale :help %wyświetla klucz dopasowywania nawiasów. Nie pomyślałbym o wypróbowaniu prefiksu podkreślenia, czy to jakiś wzór w dokumentacji vima? Czy są jakieś inne „specjalne” rzeczy do wypróbowania podczas szukania pomocy?
David Pope
2
@David: helppolecenie przeskakuje do znacznika. Możesz zobaczyć dostępne tagi za pomocą :h help-tags. Możesz także użyć uzupełniania wiersza poleceń, aby zobaczyć pasujące tagi: :h cmdline<Ctrl-D>(lub :h cmdline<Tab>jeśli odpowiednio ustawisz wildmode)
Eugene Yarmash
1
Musiałem użyć cmap w!! w !sudo tee % > /dev/nullw moim pliku .vimrc, aby to zadziałało. Czy %zgubił się w powyższej odpowiedzi? (Nie ma tutaj eksperta vim.)
dmmfll
@DMfll Tak, to prawda. Polecenie w odpowiedzi spowoduje, sudo tee > /dev/null /path/to/current/fileże tak naprawdę nie ma to sensu. (Zamierzam to edytować)
jazzpi
1
@jazzpi: Mylisz się. Powłoki tak naprawdę nie dbają o to, gdzie w linii poleceń wykonujesz przekierowanie pliku.
Eugene Yarmash
19

Działa to również dobrze:

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

Jest to inspirowane komentarzem @Nathana Longa.

UWAGA :

"należy użyć zamiast tego, 'ponieważ chcemy %rozszerzyć przed przejściem do powłoki.

feihu
źródło
11
Chociaż może to działać, daje również sudo dostęp do wielu programów (sh i cat). Inne przykłady mogą być bardziej bezpieczne, zastępując teeje, /usr/bin/teeaby zapobiec atakom modyfikującym PATH.
idbrii
18

:w - Napisz plik.

!sudo - Wywołaj polecenie sudo powłoki.

tee- Dane wyjściowe polecenia write (vim: w) przekierowano za pomocą tee. % To nic innego jak bieżąca nazwa pliku, tj. /Etc/apache2/conf.d/mediawiki.conf. Innymi słowy, polecenie tee jest uruchamiane jako root i pobiera standardowe dane wejściowe i zapisuje je w pliku reprezentowanym przez%. Spowoduje to jednak ponowne załadowanie pliku (naciśnij L, aby załadować zmiany w samym vimie):

link do samouczka

kev
źródło
9

Zaakceptowana odpowiedź obejmuje to wszystko, dlatego podam kolejny przykład skrótu , którego używam, do zapisu.

Dodaj go do swojego etc/vim/vimrc(lub ~/.vimrc):

  • cnoremap w!! execute 'silent! write !sudo tee % >/dev/null' <bar> edit!

Gdzie:

  • cnoremap: mówi vimowi, że następujący skrót ma zostać skojarzony z linią poleceń.
  • w!!: sam skrót.
  • execute '...': polecenie, które wykonuje następujący ciąg.
  • silent!: uruchom to po cichu
  • write !sudo tee % >/dev/null: pytanie OP, dodano przekierowanie wiadomości, NULLaby wykonać czyste polecenie
  • <bar> edit!: ta sztuczka jest wisienką na torcie: wywołuje także editpolecenie przeładowania bufora, a następnie uniknięcia komunikatów takich jak bufor się zmienił . <bar>jest jak napisać symbol potoku, aby oddzielić tutaj dwa polecenia.

Mam nadzieję, że to pomoże. Zobacz także inne problemy:

Dr Beco
źródło
cichy! wyłączyłem monit o hasło, więc go nie widzisz
Andy Ray
7

Chciałbym zasugerować inne podejście do „Oups, o których zapomniałem pisać sudopodczas otwierania pliku” :

Zamiast odbierać permission deniedi :w!!muszę pisać , bardziej elegancko jest mieć vimpolecenie warunkowe, które działa, sudo vimjeśli właścicielem pliku jestroot .

Jest to równie łatwe do wdrożenia (mogą być nawet bardziej eleganckie implementacje, oczywiście nie jestem bash-guru):

function vim(){
  OWNER=$(stat -c '%U' $1)
  if [[ "$OWNER" == "root" ]]; then
    sudo /usr/bin/vim $*;
  else
    /usr/bin/vim $*;
  fi
}

I działa naprawdę dobrze.

Jest to bardziej bashskoncentrowane podejście niżvim więc nie wszystkim może się to podobać.

Oczywiście:

  • istnieją przypadki użycia, w których się nie powiedzie (gdy właściciel pliku nie jest, rootale wymagasudo , ale funkcję można edytować i tak)
  • nie ma sensu, gdy używa się go tylko vimdo odczytu pliku (jeśli o mnie chodzi, używam taillub catdo małych plików)

Uważam jednak, że zapewnia to znacznie lepszą obsługę programistów , o czym IMHO często zapomina podczas korzystania bash. :-)

Augustin Riedinger
źródło
5
Pamiętaj tylko, że jest to o wiele mniej wybaczające. Osobiście większość moich błędów to głupie błędy. Wolę więc podejść do głowy, gdy robię coś, co może być zarówno głupie, jak i konsekwentne. Jest to oczywiście kwestia preferencji, ale eskalacja przywilejów powinna być czynem sumiennym. Także: jeśli doświadczasz tego tak często, że tworzysz „: w !!” dość kłopotów, aby cicho automatycznie sudo (ale tylko jeśli właściciel = root); możesz sprawdzić swój bieżący przepływ pracy.
Payne
Interesujący komentarz, choć należy być świadomym, ponieważ podczas otwierania pliku, gdy rootpytane jest hasło.
Augustin Riedinger
Ach! Jest różnica. To zależy, czy użytkownik sudo ma ustawiony „NOPASSWD”, czy nie.
Payne
Zatem posiadanie NOPASSWD jest mniej wybaczające ... :)
Augustin Riedinger
4

DLA NEOVIMA

Z powodu problemów z rozmowami interaktywnymi ( https://github.com/neovim/neovim/issues/1716 ) używam tego dla neovim, w oparciu o odpowiedź dr Beco:

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

Otworzy się okno dialogowe z ssh-askpasspytaniem o hasło sudo.

dbranco
źródło