¿Cómo funciona el truco vim "escribir con sudo"?

1413

Muchos de ustedes probablemente hayan visto el comando que les permite escribir en un archivo que necesita permiso de root, incluso cuando olvidaron abrir vim con sudo:

:w !sudo tee %

Lo que pasa es que no entiendo exactamente lo que está sucediendo aquí.

Ya he imaginado esto: wes para esto

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

entonces pasa todas las líneas como entrada estándar.

La !sudo teeparte llama teecon privilegios de administrador.

Para que todo tenga sentido, %deberían mostrar el nombre de archivo (como parámetro para tee), pero no puedo encontrar referencias sobre la ayuda para este comportamiento.

tl; dr ¿Podría alguien ayudarme a diseccionar este comando?

Doppelganger
fuente
55
@Nathan: ¿ :w !sudo cat > %No funcionaría tan bien y no contaminaría la producción estándar?
Bjarke Freund-Hansen
51
@bjarkef: no, eso no funciona. En ese caso, sudose aplica a cat, pero no a >, por lo que no está permitido. Puede intentar ejecutar todo el comando en una subshell sudo, como :w !sudo sh -c "cat % > yams.txt", pero eso tampoco funcionará, porque en la subshell, %es nulo; Vaciará el contenido de su archivo.
Nathan Long
3
Solo deseo agregar que después de escribir ese comando, puede aparecer un mensaje de advertencia. Si es así, presione L. Luego, se le pedirá que presione enter. Hazlo y finalmente tendrás tu archivo guardado.
pablofiumara
99
@NathanLong @knittl: en :w !sudo sh -c "cat >%"realidad funciona tan bien como sudo tee %porque Vim sustituye el nombre del archivo %antes de que llegue a la subshell. Sin embargo, ninguno de ellos funciona si el nombre de archivo tiene espacios; tienes que hacer :w !sudo sh -c "cat >'%'"o :w !sudo tee "%"arreglar eso.
Han Seoul-Oh,
1
Guarde usando: W y vuelva a cargar el archivo: comando W: ejecutar ': silencio w! Sudo tee%> / dev / null' | :¡editar!
Diego Roberto Dos Santos

Respuestas:

1613

En :w !sudo tee %...

% significa "el archivo actual"

Como eugene y señaló , de %hecho significa "el nombre del archivo actual", que se pasa para teeque sepa qué archivo sobrescribir.

(En los comandos de sustitución, es ligeramente diferente; como se :help :%muestra, es equal to 1,$ (the entire file)(gracias a @Orafu por señalar que esto no evalúa el nombre del archivo). Por ejemplo, :%s/foo/barsignifica " en el archivo actual , reemplace las ocurrencias de foocon bar". algo de texto antes de escribir :s, verá que las líneas resaltadas toman el lugar %como su rango de sustitución).

:w no está actualizando su archivo

Una parte confusa de este truco es que podría pensar que :westá modificando su archivo, pero no lo es. Si abrió y modificó file1.txt, luego ejecutó :w file2.txt, sería un "guardar como"; file1.txtno se modificaría, pero el contenido actual del búfer se enviaría a file2.txt.

En lugar de file2.txt, puede sustituir un comando de shell para recibir el contenido del búfer . Por ejemplo, :w !catsolo mostrará los contenidos.

Si Vim no se ejecutó con acceso sudo, :wno puede modificar un archivo protegido, pero si pasa el contenido del búfer al shell, se puede ejecutar un comando en el shell con sudo . En este caso, usamos tee.

Camiseta de comprensión

En cuanto a tee, imagine el teecomando como una tubería en forma de T en una situación de tubería de bash normal: dirige la salida a los archivos especificados y también lo envía a la salida estándar , que puede capturarse con el siguiente comando canalizado.

Por ejemplo, en ps -ax | tee processes.txt | grep 'foo', la lista de procesos se escribirá en un archivo de texto y se pasará a grep.

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

(Diagrama creado con Asciiflow ).

Vea la teepágina del manual para más información.

Tee como un truco

En la situación que describe su pregunta, usar teees un hack porque estamos ignorando la mitad de lo que hace . sudo teeescribe en nuestro archivo y también envía el contenido del búfer a la salida estándar, pero ignoramos la salida estándar . No necesitamos pasar nada a otro comando canalizado en este caso; solo lo estamos utilizando teecomo una forma alternativa de escribir un archivo y para poder llamarlo sudo.

Hacer este truco fácil

Puedes agregar esto a tu .vimrcpara hacer que este truco sea fácil de usar: solo escribe :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 arroja explícitamente la salida estándar, ya que, como dije, no necesitamos pasar nada a otro comando canalizado.

Nathan Long
fuente
147
Especialmente como tu notación "¡w!" que es tan fácil de recordar después de usar "sudo !!" en la línea de comando.
Aidan Kane
11
Entonces, esto se usa teepor su capacidad de escribir stdin en un archivo. Me sorprende que no haya un programa cuyo trabajo sea hacer eso (encontré un programa del que nunca escuché llamar spongeque hace esto). Supongo que el típico "escribir una secuencia en un archivo" es realizado por un shell incorporado. ¿Vim !{cmd}no bifurca un caparazón (en cmdcambio, se bifurca )? Quizás algo que sea más obvio sería usar alguna variante de trabajo en sh -c ">"lugar de tee.
Steven Lu
2
@ Steven Lu: spongees parte del moreutilspaquete en casi todas las distribuciones, excepto en las distribuciones basadas en Debian. moreutilstiene algunas herramientas bastante buenas que están a la par con herramientas más comunes como xargsy tee.
Suiza
10
¿Cómo expandir este alias para decirle también a vim que cargue automáticamente el contenido del archivo modificado al búfer actual? Me lo pide, ¿cómo automatizarlo?
Zlatko
10
@ user247077: en ese caso, se catejecuta como root y el shell redirige la salida, que no se ejecuta como root. Es lo mismo que echo hi > /read/only/file.
WhyNotHugo
99

En la línea de comando ejecutada, %representa el nombre del archivo actual . Esto está documentado en :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.

Como ya descubrió, :w !cmdcanaliza el contenido del búfer actual a otro comando. Lo que teehace es copiar la entrada estándar a uno o más archivos, y también a la salida estándar. Por lo tanto, :w !sudo tee % > /dev/nullescribe efectivamente el contenido del búfer actual en el archivo actual mientras es root . Otro comando que se puede usar para esto es dd:

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

Como acceso directo, puede agregar esta asignación a su .vimrc:

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

Con lo anterior, puede escribir :w!!<Enter>para guardar el archivo como root.

Eugene Yarmash
fuente
1
Interesante, :help _%muestra lo que ingresó, pero :help %muestra la clave de coincidencia de llaves. No hubiera pensado probar el prefijo de subrayado, ¿es ese un patrón de algún tipo en la documentación de vim? ¿Hay otras cosas 'especiales' para probar al buscar ayuda?
David Pope
2
@David: el helpcomando salta a una etiqueta. Puede ver las etiquetas disponibles con :h help-tags. También puede usar la finalización de la línea de comandos para ver las etiquetas coincidentes: :h cmdline<Ctrl-D>(o :h cmdline<Tab>si lo configura en wildmodeconsecuencia)
Eugene Yarmash
1
Tuve que usar cmap w!! w !sudo tee % > /dev/nullen mi archivo .vimrc para que esto funcionara. ¿Está %mal ubicado en la respuesta anterior? (No hay ningún experto vim aquí.)
dmmfll
@DMfll Sí, lo es. El comando en la respuesta daría lugar a sudo tee > /dev/null /path/to/current/filelo que realmente no tiene sentido. (Voy a editar eso)
jazzpi
1
@jazzpi: Estás equivocado. A los shells en realidad no les importa en qué parte de la línea de comandos realice la redirección de archivos.
Eugene Yarmash
19

Esto también funciona bien:

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

Esto está inspirado en el comentario de @Nathan Long.

AVISO :

"debe usarse en lugar de 'porque queremos %expandirnos antes de pasar a shell.

feihu
fuente
11
Si bien esto puede funcionar, también le da acceso a sudo a múltiples programas (sh y cat). Los otros ejemplos podrían ser más segura mediante la sustitución teecon /usr/bin/teepara prevenir ataques PATH-modificación.
idbrii
18

:w - Escribe un archivo.

!sudo - Llamar al comando shell sudo.

tee- La salida del comando write (vim: w) redirigido usando tee. El% no es más que el nombre del archivo actual, es decir, /etc/apache2/conf.d/mediawiki.conf. En otras palabras, el comando tee se ejecuta como root y toma una entrada estándar y lo escribe en un archivo representado por%. Sin embargo, esto le indicará que vuelva a cargar el archivo nuevamente (presione L para cargar los cambios en vim):

enlace tutorial

kev
fuente
9

La respuesta aceptada lo cubre todo, así que solo daré otro ejemplo de un atajo que uso, para el registro.

Agréguelo a su etc/vim/vimrc(o ~/.vimrc):

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

Dónde:

  • cnoremap: le dice a vim que el siguiente acceso directo se asociará en la línea de comando.
  • w!!: el atajo en sí.
  • execute '...': un comando que ejecuta la siguiente cadena.
  • silent!: ejecutarlo en silencio
  • write !sudo tee % >/dev/null: la pregunta OP, agregó una redirección de mensajes NULLpara hacer un comando limpio
  • <bar> edit!: este truco es la guinda del pastel: también llama al editcomando para volver a cargar el búfer y luego evitar mensajes como que el búfer ha cambiado . <bar>es cómo escribir el símbolo de tubería para separar dos comandos aquí.

Espero eso ayude. Ver también para otros problemas:

Dr. Beco
fuente
¡silencio! deshabilitó el aviso de contraseña para que no lo vea
Andy Ray
7

Me gustaría sugerir otro enfoque para el problema "Oups que olvidé escribir sudoal abrir mi archivo" :

En lugar de recibir un permission denied, y tener que escribir :w!!, me parece más elegante tener un vimcomando condicional que lo hace sudo vimsi el propietario del archivo es root.

Esto es tan fácil de implementar (incluso podría haber implementaciones más elegantes, claramente no soy un bash-guru):

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

Y funciona muy bien.

Este es un bashenfoque más centrado que vimuno, por lo que no a todos les gustaría.

Por supuesto:

  • hay casos de uso en los que fallará (cuando el propietario del archivo no lo es rootpero lo requiere sudo, pero la función se puede editar de todos modos)
  • no tiene sentido cuando se usa vimpara leer solo un archivo (en lo que a mí respecta, uso tailo catpara archivos pequeños)

Pero creo que esto brinda una experiencia de usuario de desarrollo mucho mejor , que es algo que en mi humilde opinión tiende a olvidarse cuando se usa bash. :-)

Augustin Riedinger
fuente
55
Solo tenga en cuenta que esto es mucho menos indulgente. Personalmente, la mayoría de mis errores son estúpidos. Así que prefiero ponerme al tanto cuando estoy haciendo algo que puede ser estúpido y consecuente. Por supuesto, esto es una cuestión de preferencia, pero la escalada de privilegios debe ser un acto de conciencia. Además: si experimenta esto con tanta frecuencia como para hacer ": w !!" suficiente molestia para silenciar automáticamente sudo (pero solo si propietario = root); es posible que desee examinar su flujo de trabajo actual.
Payne
Comentario interesante, aunque uno debe ser consciente porque cuando se abre el archivo mientras rootse consulta la contraseña.
Augustin Riedinger el
Ah! Ahí está la diferencia. Depende si el usuario de sudo tiene "NOPASSWD" configurado o no.
Payne
Entonces tener NOPASSWD es lo que menos perdona ... :)
Augustin Riedinger
4

Para Neovim

Debido a problemas con las llamadas interactivas ( https://github.com/neovim/neovim/issues/1716 ), estoy usando esto para neovim, según la respuesta del Dr. Beco:

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

Esto abrirá un cuadro de diálogo con ssh-askpassla solicitud de la contraseña de sudo.

dbranco
fuente