Comment fonctionne l'astuce «écrire avec sudo» de vim?

1413

Beaucoup d'entre vous ont probablement vu la commande qui vous permet d'écrire sur un fichier nécessitant une autorisation root, même si vous avez oublié d'ouvrir vim avec sudo:

:w !sudo tee %

Le fait est que je ne comprends pas exactement ce qui se passe ici.

Je l'ai déjà compris: wc'est pour ça

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

il passe donc toutes les lignes en entrée standard.

La !sudo teepartie appelle teeavec des privilèges d'administrateur.

Pour que tout ait un sens, le %devrait afficher le nom de fichier (en tant que paramètre pour tee), mais je ne trouve pas de références dans l'aide pour ce comportement.

tl; dr Quelqu'un pourrait-il m'aider à disséquer cette commande?

Doppelganger
la source
5
@Nathan: Ne :w !sudo cat > %fonctionnerait pas aussi bien et ne polluerait pas la sortie standard?
Bjarke Freund-Hansen
51
@bjarkef - non, cela ne fonctionne pas. Dans ce cas, sudoest appliqué à cat, mais pas à >, il n'est donc pas autorisé. Vous pouvez essayer d'exécuter la commande entière dans un sous-shell sudo, comme :w !sudo sh -c "cat % > yams.txt", mais cela ne fonctionnera pas non plus, car dans le sous-shell, %est nul; vous supprimerez le contenu de votre fichier.
Nathan Long
3
Je souhaite simplement ajouter qu'après avoir tapé cette commande, un message d'avertissement peut apparaître. Si c'est le cas, appuyez sur L. Ensuite, vous serez invité à appuyer sur Entrée. Faites et vous aurez enfin votre fichier enregistré.
pablofiumara
9
@NathanLong @knittl: :w !sudo sh -c "cat >%"fonctionne en fait aussi bien que sudo tee %parce que Vim remplace le nom de fichier %avant qu'il n'atteigne le sous-shell. Cependant, aucun d'eux ne fonctionne si le nom de fichier contient des espaces; vous devez faire :w !sudo sh -c "cat >'%'"ou :w !sudo tee "%"résoudre ce problème.
Han Seoul-Oh
1
Enregistrez en utilisant: W et rechargez le fichier: commande W: exécutez ': silent w! Sudo tee%> / dev / null' | :Éditer!
Diego Roberto Dos Santos

Réponses:

1613

Dans :w !sudo tee %...

% signifie "le fichier courant"

Comme l'a souligné eugene y , %cela signifie en effet "le nom du fichier actuel", qui est transmis pour teequ'il sache à quel fichier écraser.

(Dans les commandes de substitution, c'est légèrement différent; comme le :help :%montre, c'est equal to 1,$ (the entire file)(merci à @Orafu d'avoir souligné que cela n'évalue pas le nom du fichier). Par exemple, :%s/foo/barsignifie " dans le fichier actuel , remplacez les occurrences de foopar bar". Si vous mettez en surbrillance du texte avant de taper :s, vous verrez que les lignes en surbrillance %remplacent votre plage de substitution.)

:w ne met pas à jour votre fichier

Une partie déroutante de cette astuce est que vous pensez peut-être :wmodifier votre fichier, mais ce n'est pas le cas. Si vous avez ouvert et modifié file1.txt, puis exécuté :w file2.txt, ce serait un "enregistrer sous"; file1.txtne serait pas modifié, mais le contenu du tampon actuel serait envoyé à file2.txt.

Au lieu de file2.txt, vous pouvez remplacer une commande shell pour recevoir le contenu du tampon . Par exemple, :w !cataffichera simplement le contenu.

Si Vim n'a pas été exécuté avec l'accès sudo, :wil ne peut pas modifier un fichier protégé, mais s'il transmet le contenu du tampon au shell, une commande dans le shell peut être exécutée avec sudo . Dans ce cas, nous utilisons tee.

Comprendre le tee

Quant à tee, imaginez la teecommande comme un tuyau en forme de T dans une situation de tuyauterie bash normale: elle dirige la sortie vers le (s) fichier (s) spécifié (s) et l' envoie également vers la sortie standard , qui peut être capturée par la prochaine commande canalisée.

Par exemple, dans ps -ax | tee processes.txt | grep 'foo', la liste des processus sera écrite dans un fichier texte et transmise à grep.

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

(Diagramme créé avec Asciiflow .)

Voir la teepage de manuel pour plus d'informations.

Tee comme un hack

Dans la situation décrite dans votre question, l' utilisation teeest un hack car nous ignorons la moitié de ce qu'elle fait . sudo teeécrit dans notre fichier et envoie également le contenu du tampon à la sortie standard, mais nous ignorons la sortie standard . Nous n'avons pas besoin de passer quoi que ce soit à une autre commande canalisée dans ce cas; nous utilisons juste teecomme une autre façon d'écrire un fichier et pour que nous puissions l'appeler avec sudo.

Rendre cette astuce facile

Vous pouvez l'ajouter à votre .vimrcpour rendre cette astuce facile à utiliser: il suffit de taper :w!!.

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

La > /dev/nullpartie jette explicitement la sortie standard, car, comme je l'ai dit, nous n'avons pas besoin de passer quoi que ce soit à une autre commande canalisée.

Nathan Long
la source
147
Surtout comme votre notation "w !!" ce qui est si facile à retenir après avoir utilisé "sudo !!" sur la ligne de commande.
Aidan Kane
11
Donc, cela utilise teepour sa capacité à écrire stdin dans un fichier. Je suis surpris qu'il n'y ait pas de programme dont le travail consiste à faire cela (j'ai trouvé un programme dont je n'ai jamais entendu parler appelé spongequi fait cela). Je suppose que le type "écrire un flux dans un fichier" est effectué par un shell intégré. Est-ce que Vim !{cmd}ne bifurque pas une coquille (en forçant à la cmdplace)? Peut-être que quelque chose de plus évident serait d'utiliser une variante de travail de sh -c ">"plutôt que de tee.
Steven Lu
2
@Steven Lu: spongefait partie du moreutilspaquet sur à peu près toutes les distributions sauf les distributions basées sur Debian. moreutilsa de très bons outils qui sont à égalité avec des outils plus courants comme xargset tee.
Suisse
10
Comment développer cet alias pour dire également à vim de charger automatiquement le contenu du fichier modifié dans le tampon actuel? Il me le demande, comment l'automatiser?
Zlatko
10
@ user247077: Dans ce cas, cats'exécute en tant que root et la sortie est redirigée par le shell, qui ne s'exécute pas en tant que root. C'est la même chose que echo hi > /read/only/file.
WhyNotHugo
99

Dans la ligne de commande exécutée, %représente le nom de fichier actuel . Ceci est documenté dans :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.

Comme vous l'avez déjà découvert, :w !cmdredirige le contenu du tampon actuel vers une autre commande. Qu'est tee- ce que c'est copier l'entrée standard dans un ou plusieurs fichiers, et aussi sur la sortie standard. Par conséquent, :w !sudo tee % > /dev/nullécrit efficacement le contenu du tampon actuel dans le fichier actuel tout en étant root . Une autre commande qui peut être utilisée pour cela est dd:

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

En tant que raccourci, vous pouvez ajouter ce mappage à votre .vimrc:

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

Avec ce qui précède, vous pouvez taper :w!!<Enter>pour enregistrer le fichier en tant que root.

Eugene Yarmash
la source
1
Intéressant, :help _%affiche ce que vous avez entré, mais :help %affiche la clé de correspondance d'accolade. Je n'aurais pas pensé essayer le préfixe de soulignement, est-ce un modèle quelconque dans la documentation de vim? Y a-t-il d'autres choses «spéciales» à essayer lorsque vous cherchez de l'aide?
David Pope
2
@David: La helpcommande passe à une balise. Vous pouvez voir les balises disponibles avec :h help-tags. Vous pouvez également utiliser l'achèvement de la ligne de commande pour voir les balises correspondantes: :h cmdline<Ctrl-D>(ou :h cmdline<Tab>si vous définissez en wildmodeconséquence)
Eugene Yarmash
1
J'ai dû utiliser cmap w!! w !sudo tee % > /dev/nulldans mon fichier .vimrc pour que cela fonctionne. Est-ce %mal placé dans la réponse ci-dessus? (Aucun expert vim ici.)
dmmfll
@DMfll Oui, ça l'est. La commande dans la réponse entraînerait ce sudo tee > /dev/null /path/to/current/filequi n'a pas vraiment de sens. (Je vais le modifier)
jazzpi
1
@jazzpi: Vous vous trompez. Les shells ne se soucient pas réellement de l'emplacement de la redirection de fichiers sur la ligne de commande.
Eugene Yarmash
19

Cela fonctionne également bien:

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

Ceci est inspiré par le commentaire de @Nathan Long.

AVIS :

"doit être utilisé à la place de 'car nous voulons %être développés avant de passer au shell.

feihu
la source
11
Bien que cela puisse fonctionner, il donne également accès à sudo à plusieurs programmes (sh et cat). Les autres exemples pourraient être plus sûrs en les remplaçant teepar /usr/bin/teepour empêcher les attaques de modification de PATH.
idbrii
18

:w - Écrivez un fichier.

!sudo - Appelez la commande shell sudo.

tee- La sortie de la commande write (vim: w) redirigée à l'aide de tee. Le% n'est rien d'autre que le nom de fichier actuel, c'est-à-dire /etc/apache2/conf.d/mediawiki.conf. En d'autres termes, la commande tee est exécutée en tant que root et prend une entrée standard et l'écrit dans un fichier représenté par%. Cependant, cela vous invitera à recharger à nouveau le fichier (appuyez sur L pour charger les modifications dans vim lui-même):

lien du tutoriel

kev
la source
9

La réponse acceptée couvre tout, donc je vais donner un autre exemple de raccourci que j'utilise, pour mémoire.

Ajoutez-le à votre etc/vim/vimrc(ou ~/.vimrc):

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

Où:

  • cnoremap: indique à vim que le raccourci suivant doit être associé dans la ligne de commande.
  • w!!: le raccourci lui-même.
  • execute '...': une commande qui exécute la chaîne suivante.
  • silent!: exécutez-le en silence
  • write !sudo tee % >/dev/null: la question OP, ajout d'une redirection de messages NULLpour faire une commande propre
  • <bar> edit!: cette astuce est la cerise sur le gâteau: elle appelle aussi la editcommande pour recharger le buffer puis éviter les messages tels que le buffer a changé . <bar>est de savoir comment écrire le symbole de tuyau pour séparer deux commandes ici.

J'espère que cela aide. Voir aussi pour d'autres problèmes:

Dr Beco
la source
silencieux! désactivé l'invite de mot de passe pour que vous ne le voyiez pas
Andy Ray
7

Je voudrais suggérer une autre approche au problème "Oups que j'ai oublié d'écrire sudolors de l'ouverture de mon fichier" :

Au lieu de recevoir un permission denied, et d'avoir à taper :w!!, je trouve plus élégant d'avoir une vimcommande conditionnelle qui fait sudo vimsi le propriétaire du fichier l'est root.

C'est aussi facile à implémenter (il pourrait même y avoir des implémentations plus élégantes, je ne suis clairement pas un bash-gourou):

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

Et ça marche vraiment bien.

Il s'agit d'une bashapproche plus centrée qu'une vim- donc tout le monde ne l'aimera peut-être pas.

Bien sûr:

  • il y a des cas d'utilisation où il échouera (lorsque le propriétaire du fichier ne l'est pas rootmais l'exige sudo, mais que la fonction peut être modifiée de toute façon)
  • cela n'a pas de sens lors de l'utilisation vimen lecture seule d'un fichier (en ce qui me concerne, j'utilise tailou catpour de petits fichiers)

Mais je trouve que cela apporte une bien meilleure expérience utilisateur de développement , ce qui est quelque chose que IMHO a tendance à oublier lors de l'utilisation bash. :-)

Augustin Riedinger
la source
5
Sachez simplement que c'est beaucoup moins indulgent. Personnellement, la plupart de mes erreurs sont des erreurs stupides. Je préfère donc être prévenu lorsque je fais quelque chose qui pourrait être à la fois stupide et conséquent. C'est bien sûr une question de préférence, mais l'escalade de privilèges doit être un acte de conscience. Aussi: Si vous en faites l'expérience si souvent au point de faire ": w !!" assez de gêne pour auto-sudo silencieusement (mais seulement si le propriétaire = root); vous souhaiterez peut-être examiner votre flux de travail actuel.
Payne
Commentaire intéressant, mais il faut être conscient car lorsque le fichier est ouvert, rootle mot de passe est demandé.
Augustin Riedinger
Ah! Voilà la différence. Cela dépend si l'utilisateur sudo a défini "NOPASSWD" ou non.
Payne
Alors avoir NOPASSWD est ce qui est moins indulgent ... :)
Augustin Riedinger
4

POUR NEOVIM

En raison de problèmes avec les appels interactifs ( https://github.com/neovim/neovim/issues/1716 ), je l'utilise pour neovim, sur la base de la réponse du Dr Beco:

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

Cela ouvrira une boîte de dialogue utilisant la ssh-askpassdemande du mot de passe sudo.

dbranco
la source