¿Cómo funciona el truco vim “write with sudo”?

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

:w !sudo tee % 

El caso es que no entiendo lo que está sucediendo exactamente aquí.

Ya he calculado esto: w es 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 |:!|. 

por lo tanto, pasa todas las líneas como entrada estándar.

La parte de !sudo tee llama a tee con privilegios de administrador.

Para que todo tenga sentido, el % debe dar salida al nombre de archivo (como un 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?

En :w !sudo tee %

% significa “el archivo actual”

Como señaló eugene y , % hecho significa “el nombre del archivo actual”. Otro uso para esto en Vim es en los comandos de sustitución. Por ejemplo, :%s/foo/bar significa ” en el archivo actual , reemplace las apariciones de foo con bar “. Si resalta un texto antes de escribir :s , verá que las líneas resaltadas toman el lugar de % como su rango de sustitución.

:w no está actualizando su archivo

Una parte confusa de este truco es que podrías pensar :w está modificando tu archivo, pero no es así. Si abrió y modificó el file1.txt , entonces ejecutó :w file2.txt , sería un “guardar como”; file1.txt no 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 !cat solo mostrará los contenidos.

Si Vim no se ejecutó con sudo access, su :w no puede modificar un archivo protegido, pero si pasa el contenido del buffer al shell, un comando en el shell puede ejecutarse con sudo . En este caso, usamos tee .

Comprender el tee

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

Por ejemplo, en ps -ax | tee processes.txt | grep 'foo' ps -ax | tee processes.txt | grep 'foo' 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 | | | +---------------+ 

(Diagtwig creado con Asciiflow .)

Vea la página man de tee para más información.

Tee como un truco

En la situación que describe su pregunta, usar tee es un truco porque ignoramos la mitad de lo que hace . sudo tee escribe en nuestro archivo y también envía el contenido del buffer a la salida estándar, pero ignoramos la salida estándar . No necesitamos pasar nada a otro comando canalizado en este caso; solo estamos usando tee como una forma alternativa de escribir un archivo y para poder llamarlo con sudo .

Haciendo fácil este truco

Puede agregar esto a su .vimrc para hacer que este truco sea fácil de usar: simplemente escriba :w!! .

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

La parte > /dev/null expulsa explícitamente el resultado estándar, ya que, como dije, no necesitamos pasar nada a otro comando canalizado.

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 has descubierto :w !cmd canaliza el contenido del búfer actual a otro comando. Lo que hace tee 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/null escribe de forma efectiva los contenidos del búfer en uso en el archivo actual mientras es root . Otro comando que se puede usar para esto es dd :

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

Como atajo, 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!! para guardar el archivo como raíz.

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 que % se expanda antes de pasar al shell.

:w – Escribe un archivo.

!sudo!sudo call sudo shell.

tee : la salida del comando write (vim: w) redirigido mediante 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 la entrada estándar y lo escribe en un archivo representado por%. Sin embargo, esto solicitará volver a cargar el archivo (presione L para cargar los cambios en vim):

enlace tutorial

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

Añádelo a tu etc/vim/vimrc (o ~/.vimrc ):

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

Dónde:

  • cnoremap : le dice a vim que el siguiente atajo 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 a NULL para hacer un comando limpio
  • edit! : este truco es la guinda del pastel: también llama al comando de edit para volver a cargar el búfer y luego evita mensajes como el búfer ha cambiado . es cómo escribir el símbolo de tubería para separar dos comandos aquí.

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

  • SuperUser: forzar a vim a escribir un archivo

Me gustaría sugerir otro enfoque al problema “Oups me olvidé de escribir sudo al abrir mi archivo” :

En lugar de recibir un permission denied , y tener que escribir :w!! , Me parece más elegante tener un comando vim condicional que sudo vim si 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 enfoque más basado en bash que un vim 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 es root pero requiere sudo , pero la función se puede editar de todos modos)
  • no tiene sentido usar vim para leer, solo un archivo (en lo que a mí respecta, uso tail o cat para archivos pequeños)

Pero creo que esto ofrece una mejor experiencia de usuario dev , que es algo que, en mi humilde opinión, suele olvidarse al usar bash . 🙂