Come funziona il trucco vim "write with sudo"?


1412

Molti di voi hanno probabilmente visto il comando che consente di scrivere su un file che necessita dell'autorizzazione di root, anche quando si è dimenticato di aprire vim con sudo:

:w !sudo tee %

Il fatto è che non capisco cosa sta succedendo esattamente qui.

Ho già capito questo: wè per questo

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

quindi passa tutte le linee come input standard.

La !sudo teeparte chiama teecon privilegi di amministratore.

Perché tutti abbiano un senso, %dovrebbero generare il nome del file (come parametro per tee), ma non riesco a trovare riferimenti sull'aiuto per questo comportamento.

tl; dr Qualcuno potrebbe aiutarmi a dissezionare questo comando?


5
@Nathan: :w !sudo cat > %non funzionerebbe altrettanto bene e non inquinerebbe l'output standard?
Bjarke Freund-Hansen,

51
@bjarkef - no, non funziona. In tal caso, sudoviene applicato a cat, ma non a >, quindi non è consentito. Potresti provare a eseguire l'intero comando in una subshell sudo, come :w !sudo sh -c "cat % > yams.txt", ma non funzionerà neanche, perché nella subshell %è zero; cancellerai il contenuto del tuo file.
Nathan Long,

3
Vorrei solo aggiungere che dopo aver digitato quel comando, potrebbe apparire un messaggio di avviso. In tal caso, premere L. Quindi, verrà richiesto di premere Invio. Fai e avrai finalmente il tuo file salvato.
pablofiumara,

9
@NathanLong @knittl: in :w !sudo sh -c "cat >%"realtà funziona altrettanto bene sudo tee %perché Vim sostituisce il nome del file %prima che arrivi alla subshell. Tuttavia, nessuno dei due funziona se il nome file contiene spazi; devi fare :w !sudo sh -c "cat >'%'"o :w !sudo tee "%"aggiustarlo.
Han Seoul-Oh,

1
Salvare usando: W e ricaricare il file: comando W: eseguire ': silent w! Sudo tee%> / dev / null' | :modificare!
Diego Roberto Dos Santos,

Risposte:


1612

In :w !sudo tee %...

% significa "il file corrente"

Come ha sottolineato Eugene , %significa davvero "il nome del file corrente", che viene passato in teemodo che sappia quale file sovrascrivere.

(Nei comandi di sostituzione, è leggermente diverso; come :help :%mostra, è equal to 1,$ (the entire file)(grazie a @Orafu per aver sottolineato che questo non valuta il nome del file). Ad esempio, :%s/foo/barsignifica " nel file corrente , sostituire le occorrenze di foocon bar." Se si evidenzia un po 'di testo prima di digitare :s, vedrai che le linee evidenziate prendono il posto di %come intervallo di sostituzione.)

:w non aggiorna il tuo file

Una parte confusa di questo trucco è che potresti pensare di :wmodificare il tuo file, ma non lo è. Se hai aperto e modificato file1.txt, quindi eseguito :w file2.txt, sarebbe un "salva come"; file1.txtnon verrà modificato, ma il contenuto del buffer corrente verrà inviato a file2.txt.

Invece di file2.txt, è possibile sostituire un comando shell per ricevere il contenuto del buffer . Ad esempio, :w !catvisualizzerà solo i contenuti.

Se Vim non è stato eseguito con accesso sudo, :wnon può modificare un file protetto, ma se passa il contenuto del buffer alla shell, un comando nella shell può essere eseguito con sudo . In questo caso, usiamo tee.

Capire tee

Per quanto riguarda tee, immagina il teecomando come un tubo a forma di T in una normale situazione di piping bash: dirige l'output verso i file specificati e lo invia anche all'output standard , che può essere catturato dal comando di piping successivo.

Ad esempio, in ps -ax | tee processes.txt | grep 'foo', l'elenco dei processi verrà scritto in un file di testo e passato a grep.

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

(Diagramma creato con Asciiflow .)

Vedi la teepagina man per maggiori informazioni.

Tee come un trucco

Nella situazione descritta dalla tua domanda, l' utilizzo teeè un hack perché stiamo ignorando la metà di ciò che fa . sudo teescrive nel nostro file e invia anche i contenuti del buffer all'output standard, ma ignoriamo l'output standard . In questo caso non è necessario passare nulla a un altro comando inoltrato; stiamo solo usando teeun modo alternativo di scrivere un file e in modo che possiamo chiamarlo sudo.

Rendere questo trucco facile

Puoi aggiungere questo al tuo .vimrcper rendere questo trucco facile da usare: basta digitare :w!!.

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

La > /dev/nullparte elimina esplicitamente l'output standard, poiché, come ho detto, non è necessario passare nulla a un altro comando inoltrato.


147
Soprattutto come la tua notazione "w !!" che è così facile da ricordare dopo aver usato "sudo !!" sulla riga di comando.
Aidan Kane,

11
Quindi questo utilizza teeper la sua capacità di scrivere stdin in un file. Sono sorpreso che non ci sia un programma il cui compito è quello di farlo (ho trovato un programma di cui non ho mai sentito parlare chiamato spongeche fa questo). Immagino che il tipico "scrivere un flusso su un file" sia eseguito da una shell integrata. Vim !{cmd}non fa fork di una shell (biforcazione cmd)? Forse qualcosa di più ovvio sarebbe usare una variante funzionante sh -c ">"piuttosto che tee.
Steven Lu,

2
@Steven Lu: spongefa parte del moreutilspacchetto praticamente su ogni distribuzione tranne le distribuzioni basate su Debian. moreutilsha alcuni strumenti piuttosto carini che sono alla pari con strumenti più comuni come xargse tee.
Svizzera,

10
Come espandere questo alias per dire anche a vim di caricare automaticamente il contenuto del file modificato nel buffer corrente? Me lo chiede, come automatizzarlo?
Zlatko,

10
@ user247077: in tal caso, catviene eseguito come root e l'output viene reindirizzato dalla shell, che non viene eseguita come root. È lo stesso di echo hi > /read/only/file.
WhyNotHugo,

99

Nella riga di comando eseguita, %indica il nome del file corrente . Questo è documentato in :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.

Come hai già scoperto, :w !cmdinvia il contenuto del buffer corrente a un altro comando. Ciò che teefa è copiare l'input standard in uno o più file e anche nell'output standard. Pertanto, :w !sudo tee % > /dev/nullscrive efficacemente il contenuto del buffer corrente nel file corrente pur essendo root . Un altro comando che può essere utilizzato per questo è dd:

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

Come scorciatoia, puoi aggiungere questa mappatura a .vimrc:

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

Con quanto sopra puoi digitare :w!!<Enter>per salvare il file come root.


1
Interessante, :help _%richiama ciò che hai inserito, ma :help %fa apparire la chiave di abbinamento parentesi. Non avrei pensato di provare il prefisso di sottolineatura, è un modello di qualche tipo nella documentazione di VIM? Ci sono altre cose "speciali" da provare quando cerchi aiuto?
David Pope,

2
@David: il helpcomando passa a un tag. Puoi vedere i tag disponibili con :h help-tags. Puoi anche utilizzare il completamento della riga di comando per visualizzare i tag corrispondenti: :h cmdline<Ctrl-D>(o :h cmdline<Tab>se impostato di wildmodeconseguenza)
Eugene Yarmash

1
Ho dovuto usare il cmap w!! w !sudo tee % > /dev/nullmio file .vimrc per farlo funzionare. Il %mal posto è nella risposta sopra? (Nessun esperto di vim qui.)
dmmfll

@DMfll Sì, lo è. Il comando nella risposta comporterebbe sudo tee > /dev/null /path/to/current/fileche non ha davvero senso. (Andando a modificarlo)
jazzpi,

1
@jazzpi: ti sbagli. In realtà alle shell non importa dove sulla riga di comando si esegue il reindirizzamento dei file.
Eugene Yarmash,

19

Questo funziona anche bene:

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

Questo si ispira al commento di @Nathan Long.

AVVISO :

"deve essere usato anziché 'perché vogliamo %essere espansi prima di passare alla shell.


11
Anche se questo può funzionare, offre anche l'accesso sudo a più programmi (sh e cat). Gli altri esempi potrebbero essere più sicuri sostituendoli teecon /usr/bin/teeper prevenire attacchi di modifica del PERCORSO.
idbrii,

18

:w - Scrivi un file.

!sudo - Chiama il comando shell sudo.

tee- L'output del comando write (vim: w) reindirizzato usando tee. % Non è altro che il nome del file corrente, ad esempio /etc/apache2/conf.d/mediawiki.conf. In altre parole, il comando tee viene eseguito come root e accetta input standard e lo scrive in un file rappresentato da%. Tuttavia, questo richiederà di ricaricare nuovamente il file (premi L per caricare le modifiche in vim stesso):

link tutorial


9

La risposta accettata copre tutto, quindi fornirò solo un altro esempio di una scorciatoia che uso per la cronaca.

Aggiungilo al tuo etc/vim/vimrc(o ~/.vimrc):

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

Dove:

  • cnoremap: dice a Vim che il seguente collegamento deve essere associato nella riga di comando.
  • w!!: la scorciatoia stessa.
  • execute '...': un comando che esegue la seguente stringa.
  • silent!: eseguilo silenziosamente
  • write !sudo tee % >/dev/null: la domanda OP, ha aggiunto un reindirizzamento dei messaggi NULLper eseguire un comando pulito
  • <bar> edit!: questo trucco è la ciliegina sulla torta: chiama anche il editcomando per ricaricare il buffer e quindi evitare che messaggi come il buffer siano cambiati . <bar>è come scrivere il simbolo della pipe per separare due comandi qui.

Spero che sia d'aiuto. Vedi anche per altri problemi:


silenzioso! disabilitato il prompt della password in modo da non vederlo
Andy Ray

7

Vorrei suggerire un altro approccio al problema "Oups che ho dimenticato di scrivere sudodurante l'apertura del mio file" :

Invece di ricevere un permission deniede dover digitare :w!!, trovo più elegante avere un vimcomando condizionale che fa sudo vimse il proprietario del file lo è root.

È altrettanto facile da implementare (potrebbero esserci anche implementazioni più eleganti, chiaramente non sono un guru del bash):

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

E funziona davvero bene.

Questo è un bashapproccio più incentrato di vimuno, quindi non a tutti potrebbe piacere.

Ovviamente:

  • ci sono casi d'uso in cui fallirà (quando il proprietario del file non lo è rootma richiede sudo, ma la funzione può comunque essere modificata)
  • non ha senso quando si utilizza vimper la lettura di un file (per quanto mi riguarda, uso tailo catper file di piccole dimensioni)

Ma trovo che questo porti un'esperienza utente dev molto migliore , che è qualcosa che IMHO tende a essere dimenticato durante l'utilizzo bash. :-)


5
Basta essere consapevoli del fatto che questo è molto meno tollerante. Personalmente, la maggior parte dei miei errori sono errori stupidi. Quindi preferisco avere un testa a testa quando sto facendo qualcosa che potrebbe essere sia stupido che consequenziale. Questa è ovviamente una questione di preferenza, ma l'escalation dei privilegi dovrebbe essere un atto di coscienza. Inoltre: se lo si verifica così spesso da rendere ": w !!" abbastanza fastidio per auto sudo silenzioso (ma solo se proprietario = root); potresti voler esaminare il tuo attuale flusso di lavoro.
Payne,

Commento interessante, anche se uno deve essere consapevole perché quando il file viene aperto mentre rootviene richiesta la password.
Augustin Riedinger,

Ah! C'è la differenza Dipende se l'utente sudo ha impostato "NOPASSWD" o meno.
Payne,

Quindi avere NOPASSWD è ciò che è meno indulgente ... :)
Augustin Riedinger il

4

PER NEOVIM

A causa di problemi con le chiamate interattive ( https://github.com/neovim/neovim/issues/1716 ), lo sto usando per Neovim, sulla base della risposta del dott. Beco:

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

Questo aprirà una finestra di dialogo usando la ssh-askpassrichiesta della password sudo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.