Aufruf von vi durch find | xargs kaputt mein terminal. Warum?


137

Beim Aufruf vimdurch find | xargs, wie folgt:

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

Sie erhalten eine Warnung über

Input is not from a terminal

und ein Terminal mit ziemlich kaputtem Verhalten danach. Warum ist das so?


11
Randnotiz: Sie können diesen Vorgang vollständig in vim ausführen, ohne findoder xargsüberhaupt zu verwenden. Öffnen Sie vim ohne Argumente und führen Sie :args **/*.txt<CR>den Befehl aus, um die Argumente von vim im Editor festzulegen.
Trevor Powell

3
@ Trevor Powell: In all den Jahren hat vim nie aufgehört, mich zu überraschen.
DevSolar



Antworten:


100

Wenn Sie ein Programm über aufrufen xargs, zeigt die Standardeingabe des Programms auf /dev/null. (Da xargs den ursprünglichen Standard nicht kennt , tut es das nächstbeste.)

$ 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 geht davon aus, dass die stdin mit dem steuernden Terminal identisch ist, und führt verschiedene terminalbezogene Ioctls direkt auf stdin aus. Wenn dies mit /dev/null(oder einem anderen Dateideskriptor als tty) erledigt ist, sind diese Ioctls bedeutungslos und geben ENOTTY zurück, was unbemerkt ignoriert wird.

  • Ich vermute eine spezifischere Ursache: Beim Start liest und merkt sich Vim die alten Terminaleinstellungen und stellt sie beim Beenden wieder her. In unserer Situation empfängt Vim, wenn die "alten Einstellungen" für einen Nicht-tty-fd (Dateideskriptor) angefordert werden, alle leeren Werte und alle deaktivierten Optionen und legt diese achtlos auf Ihrem Terminal fest.

    Sie können dies sehen, indem Sie ausführen vim < /dev/null, es beenden und dann ausführen stty, wodurch eine ganze Reihe von <undef>s ausgegeben werden . Unter Linux läuft stty sanedas Terminal nutzbar machen wieder (obwohl es wird solche Optionen verloren hat , wie iutf8, möglicherweise kleinere Ärgernisse später verursacht).

Sie können dies als Fehler in Vim betrachten, da es für die Terminalsteuerung geöffnet werden kann/dev/tty , dies jedoch nicht. (Irgendwann während des Startvorgangs dupliziert Vim seinen stderr in stdin, wodurch er Ihre Eingabebefehle lesen kann - von einem zum Schreiben geöffneten fd - aber selbst das wird nicht früh genug getan.)


20
+1 und für TL; DR-Leute einfach ausführenstty sane
doc_id

@rahmanisback: Die anderen Antworten und der Kommentar von Trevor boten alle Möglichkeiten, um Klemmenbrüche zu vermeiden. Ich habe die Antwort von grawity akzeptiert, weil meine Frage "Warum" war, nicht "Wie vermeide ich das?" - das wird von einer anderen Frage abgedeckt, aus der diese Frage tatsächlich hervorging .
DevSolar

@DevSolar Verstanden, aber denken Sie an frustrierte Leute wie mich, die nur googeln, um dieses Verhalten zu beseitigen. Leider haben sie jetzt genug Zeit, um das "Warum" zu studieren, was dennoch sehr interessant ist.
doc_id

4
Wenn mein Terminal so kaputt geht, benutze ich resetstattdessen stty saneund es funktioniert danach einwandfrei.
Capi Etheriel

137

(In Anlehnung an die Erklärung von grawity xargsverweist das stdinauf /dev/null.)

Die Lösung für dieses Problem besteht darin, den -oParameter hinzuzufügen xargs. Von man xargs:

-o

      Öffnen Sie stdin erneut wie /dev/ttyim untergeordneten Prozess, bevor Sie den Befehl ausführen. Dies ist nützlich, wenn Sie xargseine interaktive Anwendung ausführen möchten .

Daher sollte die folgende Codezeile für Sie funktionieren:

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

GNU xargs unterstützt diese Erweiterung seit einiger Veröffentlichung im Jahr 2017 (mit dem langen Optionsnamen --open-tty).

Bei älteren oder anderen Versionen von xargs können Sie /dev/ttydas Problem explizit lösen, indem Sie Folgendes eingeben :

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

(Das ignoremeist da, um $ 0 aufzunehmen, so dass $ @ alle Argumente von xargs sind.)


2
Wie würden Sie daraus einen Bash-Alias ​​erstellen? $@Scheint, Argumente nicht richtig zu übersetzen.
Zanegray

1
@zanegray - Sie können keinen Alias ​​erstellen, aber Sie können eine Funktion erstellen. Versuchen Sie:function vimin () { xargs sh -c 'vim "$@" < /dev/tty' vim; }
Christopher

Eine detaillierte Erklärung, wie die GNU xargs-Lösung funktioniert und warum Sie den Dummy- ignoremeString benötigen , finden Sie unter vi.stackexchange.com/a/17813
wisbucky

@ zanegray, Du kannst es zu einem Alias ​​machen. Die Zitate sind schwierig. Die Lösung finden Sie unter 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.(Es war für mich unter macOS nicht verfügbar, da ich xargs von Homebrew (der GNU-Version) installiert habe.)
localhostdotdev

33

Der einfachste Weg:

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

5
Die Hauptfrage lautete "warum" und nicht "wie man es vermeidet" und wurde vor zweieinhalb Jahren zur Zufriedenheit beantwortet.
DevSolar

5
Dies funktioniert natürlich nicht richtig, wenn Dateinamen Leerzeichen oder andere Sonderzeichen enthalten, und ist auch ein Sicherheitsrisiko.
Dejay Clayton

1
Meine Lieblingsantwort, weil es für jeden Befehl funktioniert, der Dateien auflistet, nicht nur "Suchen" oder Platzhalter. Es erfordert ein wenig Vertrauen, wie Dejay betont.
Travis Wilson

1
Dies funktioniert nicht in vielen Anwendungsfällen, für die xargs entwickelt wurde: z. B. wenn die Anzahl der Pfade sehr hoch ist (cc @ TravisWilson)
Gute Person

21

Es sollte gut funktionieren, wenn Sie die Option -exec für find verwenden, anstatt in xargs zu leiten.

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

2
Huh ... der Trick dabei ist der +(anstelle von "dem Üblichen" \;), alle gefundenen Dateien in eine Vim-Sitzung zu bringen - eine Option, die ich immer wieder vergesse. Sie haben natürlich Recht und +1 dafür. Ich benutze vim $(find ...)einfach aus Gewohnheit. Tatsächlich fragte ich mich jedoch, warum die Pipe-Operation das Terminal versaut hat, und Grawity traf dies mit seiner Erklärung.
DevSolar

2
Dies ist die beste Antwort und funktioniert unter BSD / OSX / GNU / Linux.
Kevinarpe

1
Außerdem ist find nicht die einzige Möglichkeit, eine Liste der Dateien abzurufen, die von vim gleichzeitig bearbeitet werden müssen. Ich kann grep verwenden, um alle Dateien mit einem Muster zu finden und sie gleichzeitig zu bearbeiten.
Chandranshu

8

Verwenden Sie stattdessen GNU Parallel:

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

Oder wenn Sie alle Dateien auf einmal öffnen möchten:

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

Es behandelt sogar Dateinamen wie:

My brother's 12" records.txt

Sehen Sie sich das Intro-Video an, um mehr zu erfahren: http://www.youtube.com/watch?v=OpaiGYxkSuQ


1
Nicht überall verfügbar. Die meiste Zeit arbeite ich auf Servern, auf denen ich keine zusätzlichen Tools installieren kann. Aber trotzdem danke für den Hinweis.
DevSolar

Wenn es Ihnen freigestellt ist, 'cat> file zu machen; chmod + x file 'dann können Sie GNU Parallel installieren: Es ist einfach ein Perl-Skript. Wenn Sie Manpages und ähnliches wollen, können Sie diese unter Ihrem Homedir installieren: ./configure --prefix = $ HOME && make && make install
Ole Tange

2
OK, versucht das - aber parallel öffnet nicht alle Dateien, sondern öffnet sie nacheinander . Es ist auch ein ziemlicher Bissen für eine einfache Operation. vim $(find . -name "*.txt")ist einfacher, und Sie erhalten alle Dateien auf einmal geöffnet.
DevSolar

5
@ DevSolar: Etwas unabhängig, aber beide find | xargsund $(find)werden große Probleme mit Leerzeichen in Dateinamen haben.
Grawity

2
@grawity Richtig, aber es gibt keinen einfachen Weg (den ich kenne), um es zu umgehen. Sie würden mit zu beginnen haben das Hantieren $IFS, -print0und solche Sachen, und dann nach links Sie in das Reich der One-Shot - Befehlszeile - Lösung und einen Punkt erreicht , wo man mit einem Skript kommen sollte ... es gibt einen Grund , warum Leerzeichen in Dateinamen abgeraten .
DevSolar

0

Vielleicht nicht das beste, aber hier das Skript, das ich benenne vim-open:

#!/usr/bin/env ruby

require 'shellwords'

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

wird mit vim-open a b cund ls | vim-openzum Beispiel arbeiten


Bei mehreren anderen Antworten ist zu beachten, dass die eigentliche Frage "Warum" und nicht "Wie vermeide ich das?" Lautete. (
Wofür
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.