Invocare vi tramite find | xargs rompe il mio terminale. Perché?


137

Quando si invoca vimattraverso find | xargs, in questo modo:

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

ricevi un avviso in merito

Input is not from a terminal

e un terminale con un comportamento praticamente rotto in seguito. Perché?


11
Nota a margine: è possibile eseguire questa operazione interamente all'interno di vim, non utilizzando findo xargsaffatto. Apri vim senza argomenti, quindi esegui :args **/*.txt<CR>per impostare gli argomenti di vim dall'interno dell'editor.
Trevor Powell,

3
@TrevorPowell: In tutti questi anni, Vim non ha mai smesso di stupirmi.
DevSolar



Risposte:


100

Quando si richiama un programma tramite xargs, lo stdin (input standard) del programma punta a /dev/null. (Dato che xargs non conosce lo stdin originale , fa la cosa migliore dopo.)

$ 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 si aspetta che il suo stdin sia uguale al suo terminale di controllo ed esegue direttamente vari ioctl relativi al terminale su stdin. Quando fatto su /dev/null(o qualsiasi descrittore di file non tty), questi ioctl sono privi di significato e restituiscono ENOTTY, che viene silenziosamente ignorato.

  • La mia ipotesi su una causa più specifica: all'avvio Vim legge e ricorda le vecchie impostazioni del terminale e le ripristina all'uscita. Nella nostra situazione, quando vengono richieste le "vecchie impostazioni" per un non tty fd (descrittore di file), Vim riceve tutti i valori vuoti e tutte le opzioni disabilitate e imposta incurantemente le stesse sul terminale.

    Puoi vederlo eseguendo vim < /dev/null, uscendo, quindi eseguendo stty, che genererà un sacco di <undef>s. Su Linux, l'esecuzione stty sanerenderà nuovamente utilizzabile il terminale (anche se avrà perso opzioni come iutf8, probabilmente causando fastidi minori in seguito).

Potresti considerarlo un bug in Vim, dal momento che può aprirsi /dev/ttyper il controllo terminale, ma non lo fa. (Ad un certo punto durante l'avvio, Vim duplica il suo stderr su stdin, il che gli consente di leggere i comandi di input - da un fd aperto per la scrittura - ma anche questo non è fatto abbastanza presto.)


20
+1, e per TL; le persone DR sono appena stty sane
scappate

@rahmanisback: le altre risposte, oltre al commento di Trevor, hanno fornito tutti i modi per evitare la rottura del terminale, in primo luogo. Ho accettato la risposta di Grawity, perché la mia domanda era "perché", non "come evitare" - che è coperta da un'altra domanda che in realtà ha generato questo.
DevSolar,

@DevSolar Capito, ma pensa a persone frustrate come me che semplicemente google come sbarazzarsi di quel comportamento senza, purtroppo, hanno abbastanza tempo per studiare "perché", il che è comunque molto interessante.
doc_id

4
quando il mio terminale si rompe, come questo, io uso resetinvece di stty sanee funziona bene dopo.
Capi Etheriel,

137

(A seguito di spiegazione di grawity, che xargspunta stdina /dev/null.)

La soluzione per questo problema è aggiungere il -oparametro a xargs. Da man xargs:

-o

      Riaprire stdin come /dev/ttynel processo figlio prima di eseguire il comando. Ciò è utile se si desidera xargseseguire un'applicazione interattiva.

Pertanto, la seguente riga di codice dovrebbe funzionare per te:

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

GNU xargs supporta questa estensione da qualche versione nel 2017 (con il nome dell'opzione lunga --open-tty).

Per le versioni precedenti o di altre versioni di xargs, è possibile passare esplicitamente /dev/ttyper risolvere il problema:

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

(Il file ignoremeè lì per richiedere $ 0, in modo che $ @ sia tutti gli argomenti di xargs.)


2
Come creeresti un alias bash da questo? $@non sembra tradurre correttamente gli argomenti.
zanegray,

1
@zanegray - non puoi creare un alias, ma puoi renderlo una funzione. Prova:function vimin () { xargs sh -c 'vim "$@" < /dev/tty' vim; }
Christopher,

Per una spiegazione dettagliata di come funziona la soluzione GNU xargs e perché hai bisogno della ignoremestringa fittizia , vedi vi.stackexchange.com/a/17813
wisbucky

@zanegray, puoi renderlo un alias. Le citazioni sono complicate. Vedi soluzione su 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.(Non era disponibile su macOS per me perché ho installato xargs da homebrew (quello GNU))
localhostdotdev

33

La via più facile:

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

5
La domanda principale era "perché", non "come evitarlo", ed è stata soddisfatta due anni e mezzo fa.
DevSolar

5
Questo, ovviamente, non funziona correttamente quando i nomi dei file contengono spazi o altri caratteri speciali ed è anche un rischio per la sicurezza.
Dejay Clayton,

1
La mia risposta preferita perché funziona per ogni comando che elenca i file, non solo "trova" o caratteri jolly. Richiede un po 'di fiducia, come sottolinea Dejay.
Travis Wilson,

1
Questo non funzionerà con molti casi d'uso per cui xargs è progettato: ad esempio, quando il numero di percorsi è molto elevato (cc @TravisWilson)
persona

21

Dovrebbe funzionare bene se usi l'opzione -exec su find piuttosto che eseguire il piping in xargs.

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

2
Eh ... il trucco c'è +(invece del "solito" \;) per mettere tutti i file trovati in una sessione di Vim - un'opzione di cui continuo a dimenticare. Hai ragione, ovviamente, e +1 per quello. Uso vim $(find ...)semplicemente per abitudine. Tuttavia, in realtà stavo chiedendo perché l'operazione di tubatura rovina il terminale, e la gravità lo ha inchiodato con la sua spiegazione.
DevSolar

2
Questa è la risposta migliore e funziona sia su BSD / OSX / GNU / Linux.
Kevinevpe,

1
Inoltre, find non è l'unico modo per ottenere un elenco di file che devono essere modificati contemporaneamente da VIM. Posso usare grep per trovare tutti i file con uno schema e provare a modificarli allo stesso tempo.
Chandranshu,

8

Usa invece GNU Parallel:

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

O se vuoi aprire tutti i file in una volta sola:

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

Tratta anche correttamente con nomi di file come:

My brother's 12" records.txt

Guarda il video introduttivo per saperne di più: http://www.youtube.com/watch?v=OpaiGYxkSuQ


1
Non disponibile ovunque. La maggior parte del giorno sto lavorando su server in cui non sono libero di installare strumenti aggiuntivi. Ma grazie per il suggerimento comunque.
DevSolar,

Se sei libero di fare 'cat> file; chmod + x file 'allora puoi installare GNU Parallel: è semplicemente uno script perl. Se vuoi pagine man e simili, puoi installarlo sotto il tuo homedir: ./configure --prefix = $ HOME && make && make install
Ole Tange

2
OK, ci ho provato, ma parallelamente non apre tutti i file, ma li apre in successione . È anche piuttosto un boccone per una semplice operazione. vim $(find . -name "*.txt")è più semplice e tutti i file vengono aperti contemporaneamente.
DevSolar

5
@DevSolar: un po 'estraneo, ma entrambi find | xargse $(find)avranno grossi problemi con gli spazi nei nomi dei file.
Grawity,

2
@grawity Corretto, ma non c'è modo semplice di aggirarlo (di cui sono a conoscenza). Dovresti iniziare a giocherellare $IFS, -print0e roba del genere, e poi hai lasciato il regno di una soluzione a riga di comando one-shot e hai raggiunto un punto in cui dovresti trovare uno script ... c'è un motivo per cui gli spazi nei nomi dei file sono scoraggiati .
DevSolar,

0

forse non il migliore ma qui lo script che uso (chiamato vim-open):

#!/usr/bin/env ruby

require 'shellwords'

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

funzionerà con vim-open a b ce ls | vim-openper esempio


Per quanto riguarda diverse altre risposte, nota che la vera domanda era "perché", non "come evitarlo". (Per il quale vorrei ancora indicare il commento di Trevor sotto la mia domanda come il modo più solido che non richiede script, alias o altro.)
DevSolar
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.