Invoquer vi via find | xargs casse mon terminal. Pourquoi?

137

Lors de l' appel à vimtravers find | xargs, comme ceci:

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

vous recevez un avertissement à propos de

Input is not from a terminal

et un terminal avec un comportement à peu près cassé après. Pourquoi donc?

DevSolar
la source
11
Remarque secondaire: vous pouvez effectuer cette opération entièrement dans vim, sans utiliser findou pas xargsdu tout. Ouvrez vim sans argument, puis exécutez-le :args **/*.txt<CR>pour définir les arguments de vim à partir de l'éditeur.
Trevor Powell
3
@TrevorPowell: Au cours de toutes ces années, Vim n'a jamais cessé de m'étonner.
DevSolar
Rapport de bogue GitHub: vim ne gère pas STDIN défini sur / dev / null .
Kenorb

Réponses:

100

Lorsque vous appelez un programme via xargs, le stdin (entrée standard) du programme pointe sur /dev/null. (Puisque xargs ne connait pas le stdin original , c'est la meilleure chose à faire.)

$ true | xargs filan -s
    0 chrdev / dev / null
    1 tty / dev / pts / 1
    2 tty / dev / pts / 1

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

Vim s'attend à ce que son stdin soit identique à celui de son terminal de contrôle et exécute directement divers ioctls liés au terminal sur stdin. Lorsque cela est fait sur /dev/null(ou tout descripteur de fichier non-tty), ces ioctls n'ont pas de sens et renvoient ENOTTY, ce qui est ignoré en silence.

  • À mon avis, une cause plus spécifique: Au démarrage, Vim lit et retient les anciens paramètres du terminal, puis les restaure à la sortie. Dans notre cas, lorsque les "anciens paramètres" sont demandés pour un descripteur de fichier non-tty, Vim reçoit toutes les valeurs vides et toutes les options désactivées, et définit négligemment la même chose sur votre terminal.

    Vous pouvez voir cela en exécutant vim < /dev/null, en le quittant, puis en exécutant stty, ce qui produira beaucoup de <undef>s. Sous Linux, l'exécution stty sanerendra le terminal utilisable à nouveau (bien qu'il aura perdu des options telles que iutf8, causant éventuellement des ennuis mineurs plus tard).

Vous pourriez considérer cela comme un bogue dans Vim, car il peut s'ouvrir /dev/ttypour le contrôle de terminal, mais ne le fait pas. (À un moment donné au cours du démarrage, Vim duplique son stderr sur stdin, ce qui lui permet de lire vos commandes d'entrée - depuis un fd ouvert en écriture - mais même cela n'est pas fait assez tôt.)

Grawity
la source
20
+1, et pour TL; DR, les gens viennent de courirstty sane
doc_id
@rahmanisback: Les autres réponses, ainsi que le commentaire de Trevor, fournissaient tous des moyens d'éviter la casse finale. J'ai accepté la réponse de grawity, parce que ma question était "pourquoi", pas "comment éviter" - c'est couvert par une autre question qui a en fait engendré celle-ci.
DevSolar
@DevSolar Compris, mais pensez aux personnes frustrées comme moi qui cherchent simplement à se débarrasser de ce comportement tout en ayant - malheureusement - assez de temps pour étudier le "pourquoi", ce qui est néanmoins très intéressant.
doc_id
4
quand mon terminal se casse, comme ça, je l’utilise à la resetplace stty saneet ça marche très bien après ça.
Capi Etheriel
137

(Suite à l'explication de grawity, cela xargspointe stdinvers /dev/null.)

La solution à ce problème consiste à ajouter le -oparamètre à xargs. De man xargs:

-o

      Rouvrez stdin comme /dev/ttydans le processus enfant avant d'exécuter la commande. Ceci est utile si vous souhaitez xargsexécuter une application interactive.

Ainsi, la ligne de code suivante devrait fonctionner pour vous:

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

GNU xargs supporte cette extension depuis certaines versions en 2017 (avec le nom de l'option longue --open-tty).

Pour les versions antérieures ou autres de xargs, vous pouvez explicitement vous connecter /dev/ttypour résoudre le problème:

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

(Le ignoremeest là pour prendre $ 0, donc $ @ est tous les arguments de xargs.)

James McGuigan
la source
2
Comment créerais-tu un alias bash? $@ne semble pas traduire correctement les arguments.
zanegray
1
@zanegray - vous ne pouvez pas créer d'alias, mais vous pouvez en faire une fonction. Essayez:function vimin () { xargs sh -c 'vim "$@" < /dev/tty' vim; }
Christopher
Pour une explication détaillée du fonctionnement de la solution GNU xargs et de la raison pour laquelle vous avez besoin de la ignoremechaîne factice , voir vi.stackexchange.com/a/17813
wisbucky
@zanegray, vous pouvez en faire un alias. Les citations sont délicates. Voir la solution sur vi.stackexchange.com/a/17813
wisbucky
The -J, -o, -P and -R options are non-standard FreeBSD extensions which may not be available on other operating systems.(Il n'était pas disponible sur macOS pour moi parce que j'ai installé xargs depuis homebrew (celui de GNU))
localhostdotdev
33

Le moyen le plus simple:

vim $(find . -name "*foo*")
trolol
la source
5
La question principale était "pourquoi", pas "comment l'éviter", et on y a répondu avec satisfaction il y a deux ans et demi.
DevSolar
5
Ceci, bien sûr, ne fonctionne pas correctement lorsque les noms de fichiers contiennent des espaces ou d’autres caractères spéciaux, et constitue également un risque pour la sécurité.
Dejay Clayton
1
Ma réponse préférée parce que cela fonctionne pour toutes les commandes qui répertorient des fichiers, pas seulement "trouver" ou des caractères génériques. Cela demande un peu de confiance, comme le souligne Dejay.
Travis Wilson
1
Cela ne fonctionnera pas avec de nombreux cas d'utilisation xargs est conçu pour: par exemple, lorsque le nombre de chemins est très élevé (cc @TravisWilson)
Bonne personne
21

Cela devrait fonctionner correctement si vous utilisez l'option -exec sur find plutôt que de vous connecter à xargs.

find . -type f -name filename.txt -exec vi {} + 
Chris Wraith
la source
2
Euh ... l'astuce consiste à obtenir +(au lieu de "l'habituel" \;) tous les fichiers trouvés en une session Vim - une option que je ne cesse d'oublier. Vous avez raison, bien sûr, et +1 pour cela. J'utilise vim $(find ...)simplement par habitude. Cependant, je demandais en fait pourquoi le système de tuyauterie visait le terminal, et Grawity l'a expliqué dans ses explications.
DevSolar
2
C'est la meilleure réponse et cela fonctionne à la fois sous BSD / OSX / GNU / Linux.
kevinarpe
1
En outre, find n'est pas le seul moyen d'obtenir une liste de fichiers devant être modifiés simultanément par vim. Je peux utiliser grep pour trouver tous les fichiers avec un motif et essayer de les éditer en même temps.
Chandranshu
8

Utilisez GNU Parallel à la place:

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

Ou si vous voulez ouvrir tous les fichiers en une fois:

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

Il traite même correctement les noms de fichiers tels que:

My brother's 12" records.txt

Regardez la vidéo d'introduction pour en savoir plus: http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
la source
1
Pas disponible partout. La majeure partie de la journée, je travaille sur des serveurs où je ne suis pas libre d’installer des outils supplémentaires. Mais merci pour l'allusion quand même.
DevSolar
Si vous êtes libre de faire 'cat> file; chmod + x fichier ', vous pouvez installer GNU Parallel: c’est simplement un script Perl. Si vous voulez les pages de manuel et tel, vous pouvez l' installer sous votre homedir: ./configure prefix = $ HOME && make && make install
Ole Tange
2
OK, j'ai essayé - mais parallèlement, tous les fichiers ne sont pas ouverts, il les ouvre successivement . C'est aussi une bouchée pour une opération simple. vim $(find . -name "*.txt")est plus simple, et vous obtenez tous les fichiers ouverts à la fois.
DevSolar
5
@ DevSolar: Un peu sans rapport, mais les deux find | xargset $(find)auront de gros problèmes d'espaces dans les noms de fichiers.
Grawity
2
@grawity Correct, mais il n'y a pas de moyen facile de le contourner (à ma connaissance). Vous devez commencer à bricoler $IFS, -print0et cetera, puis vous avez quitté le domaine des solutions de ligne de commande one-shot pour atteindre un point où vous devriez créer un script ... il y a une raison pour laquelle les espaces dans les noms de fichiers sont découragés .
DevSolar
0

peut-être pas le meilleur mais voici le script que j'utilise (nommé vim-open):

#!/usr/bin/env ruby

require 'shellwords'

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

travaillera avec vim-open a b cet ls | vim-openpar exemple

localhostdotdev
la source
Pour ce qui est de plusieurs autres réponses, notez que la vraie question était "pourquoi" et non "comment l'éviter". (Pour lequel je signalerais toujours le commentaire de Trevor sous ma question comme le moyen le plus solide qui n'exige pas de script, d'alias ou quoi que ce soit.)
DevSolar