Invocación de vi a través de find | Xargs rompe mi terminal. ¿Por qué?


137

Cuando se invoca vima través find | xargs, de esta manera:

find . -name "*.txt" | xargs vim

recibes una advertencia sobre

Input is not from a terminal

y una terminal con un comportamiento bastante roto después. ¿Porqué es eso?


11
Nota al margen: puede realizar esta operación completamente dentro de vim, sin usar findo xargsen absoluto. Abra vim sin argumentos, luego ejecútelo :args **/*.txt<CR>para configurar los argumentos de vim desde el interior del editor.
Trevor Powell

3
@TrevorPowell: En todos estos años, vim nunca dejó de sorprenderme.
DevSolar



Respuestas:


100

Cuando invoca un programa a través de xargs, el stdin del programa (entrada estándar) apunta a /dev/null. (Dado que xargs no conoce el stdin original , hace lo siguiente mejor).

$ verdadero | xargs Filan -s
    0 chrdev / dev / null
    1 tty / dev / pts / 1
    2 tty / dev / pts / 1

$ verdadero | xargs ls -l / dev / fd /

Vim espera que su stdin sea el mismo que su terminal de control y realiza varios ioctl relacionados con el terminal en stdin directamente. Cuando se hace en /dev/null(o cualquier descriptor de archivo que no sea tty), esos ioctls no tienen sentido y devuelven ENOTTY, que se ignora en silencio.

  • Mi suposición es una causa más específica: en el inicio, Vim lee y recuerda la configuración anterior del terminal, y la restaura de nuevo al salir. En nuestra situación, cuando se solicitan las "configuraciones antiguas" para un fd (descriptor de archivo) no tty, Vim recibe todos los valores vacíos y todas las opciones deshabilitadas, y descuidadamente establece lo mismo en su terminal.

    Puede ver esto ejecutando vim < /dev/null, saliendo de él, luego ejecutando stty, lo que generará una gran cantidad de <undef>s. En Linux, la ejecución stty sanehará que el terminal se pueda volver a usar (aunque habrá perdido opciones tales como iutf8, posiblemente, causando molestias menores más adelante).

Podría considerar esto un error en Vim, ya que puede abrirse /dev/ttypara el control del terminal, pero no lo hace. (En algún momento durante el inicio, Vim duplica su stderr a stdin, lo que le permite leer sus comandos de entrada, desde un fd abierto para escribir, pero incluso eso no se hace lo suficientemente temprano).


20
+1, y para TL; la gente de DR acaba de correrstty sane
doc_id

@rahmanisback: Las otras respuestas, más el comentario de Trevor, proporcionaron formas de evitar la rotura de terminales en primer lugar. Acepté la respuesta de Grawity, porque mi pregunta era "por qué", no "cómo evitar", eso está cubierto por otra pregunta que realmente generó esta.
DevSolar

@DevSolar Entendido, pero piense en las personas frustradas como yo que simplemente buscan en Google cómo deshacerse de ese comportamiento y, desafortunadamente, no tienen suficiente tiempo en este momento para estudiar "por qué", lo cual es muy interesante.
doc_id

44
cuando mi terminal se rompe, así, lo uso en resetlugar de stty saney funciona bien después de eso.
Capi Etheriel

137

(Siguiendo con la explicación de Grawity, eso xargsapunta stdina /dev/null).

La solución para este problema es agregar el -oparámetro a xargs. De man xargs:

-o

      Vuelva a abrir stdin como /dev/ttyen el proceso secundario antes de ejecutar el comando. Esto es útil si desea xargsejecutar una aplicación interactiva.

Por lo tanto, la siguiente línea de código debería funcionar para usted:

encontrar . -name "* .txt" | xargs -o vim

GNU xargs admite esta extensión desde algún lanzamiento en 2017 (con el nombre largo de la opción --open-tty).

Para versiones anteriores u otras de xargs, puede pasar explícitamente /dev/ttypara resolver el problema:

find . -name "*.txt" | xargs bash -c '</dev/tty vim "$@"' ignoreme

(El ignoremeestá ahí para tomar $ 0, por lo que $ @ es todos los argumentos de xargs).


2
¿Cómo crearías un alias bash a partir de esto? $@no parece estar traduciendo argumentos correctamente.
zanegray

1
@zanegray: no puedes crear un alias, pero puedes convertirlo en una función. Prueba:function vimin () { xargs sh -c 'vim "$@" < /dev/tty' vim; }
Christopher

Para obtener una explicación detallada de cómo funciona la solución GNU xargs y por qué necesita la ignoremecadena ficticia , consulte vi.stackexchange.com/a/17813
wisbucky el

@zanegray, puedes convertirlo en un alias. Las citas son complicadas. Vea la solución en vi.stackexchange.com/a/17813
wisbucky el

The -J, -o, -P and -R options are non-standard FreeBSD extensions which may not be available on other operating systems.(No estaba disponible en macOS porque instalé xargs de homebrew (el GNU))
localhostdotdev

33

La forma más fácil:

vim $(find . -name "*foo*")

55
La pregunta principal era "por qué", no "cómo evitarlo", y se respondió satisfactoriamente hace dos años y medio.
DevSolar

55
Esto, por supuesto, no funciona correctamente cuando los nombres de archivo contienen espacios u otros caracteres especiales, y también es un riesgo de seguridad.
Dejay Clayton

1
Mi respuesta favorita porque funciona para cada comando que enumera archivos, no solo "buscar" o comodines. Requiere un poco de confianza, como señala Dejay.
Travis Wilson

1
Esto no funcionará con muchos casos de uso. Xargs está diseñado para: por ejemplo, cuando el número de rutas es muy alto (cc @ TravisWilson)
Buena persona

21

Debería funcionar bien si usa la opción -exec en buscar en lugar de canalizar en xargs.

find . -type f -name filename.txt -exec vi {} + 

2
Eh ... el truco está el +(en lugar de "lo de siempre" \;) para obtener todos los archivos que se encuentran en una sesión de Vim - una opción que mantener a olvidar. Tienes razón, por supuesto, y +1 para eso. Lo uso vim $(find ...)simplemente por costumbre. Sin embargo, en realidad estaba preguntando por qué la operación de la tubería arruina la terminal, y Grawity lo clavó con su explicación.
DevSolar

2
Esta es la mejor respuesta y funciona tanto en BSD / OSX / GNU / Linux.
kevinarpe

1
Además, find no es la única forma de obtener una lista de archivos que vim debe editar simultáneamente. Puedo usar grep para encontrar todos los archivos con un patrón e intentar editarlos al mismo tiempo también.
Chandranshu

8

Use GNU Parallel en su lugar:

find . -name "*.txt" | parallel -j1 --tty vim

O si desea abrir todos los archivos de una vez:

find . -name "*.txt" | parallel -Xj1 --tty vim

Incluso trata correctamente con nombres de archivo como:

My brother's 12" records.txt

Mire el video de introducción para obtener más información: http://www.youtube.com/watch?v=OpaiGYxkSuQ


1
No disponible en todas partes. La mayor parte del día estoy trabajando en servidores en los que no tengo libertad para instalar herramientas adicionales. Pero gracias por la pista de todos modos.
DevSolar

Si tiene la libertad de hacer 'cat> file; chmod + x file ', entonces puede instalar GNU Parallel: es simplemente un script perl. Si desea páginas de manual y similares, puede instalarlo bajo su nombre de usuario: ./configure --prefix = $ HOME && make && make install
Ole Tange

2
Bien, lo intenté, pero el paralelo no abre todos los archivos, los abre en sucesión . También es bastante bocado para una operación simple. vim $(find . -name "*.txt")es más simple y obtienes todos los archivos abiertos a la vez.
DevSolar

55
@DevSolar: Algo relacionado, pero ambos find | xargsy $(find)tendrá grandes problemas con los espacios en los nombres de archivo.
Grawity

2
@grawity Correcto, pero no hay manera fácil de evitarlo (que yo sepa). Habría que empezar a toquetear $IFS, -print0y esas cosas, y después de salir del ámbito de una solución de línea de comandos de una sola vez y llegó a un punto en el que debe llegar a un guión ... hay una razón por la cual no se animan espacios en los nombres de archivo .
DevSolar

0

tal vez no sea el mejor, pero aquí está el script que uso (llamado vim-open):

#!/usr/bin/env ruby

require 'shellwords'

inputs = (ARGV + (STDIN.tty? ? [] : STDIN.to_a)).map(&:strip)
exec("</dev/tty vim #{inputs.flatten.shelljoin}")

trabajará con vim-open a b cy ls | vim-openpor ejemplo


En cuanto a varias otras respuestas, tenga en cuenta que la pregunta real era "por qué", no "cómo evitarla". (Por lo cual todavía señalaría el comentario de Trevor bajo mi pregunta como la forma más sólida que no requiere secuencias de comandos, alias ni nada.)
DevSolar
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.