xargs e vi - "L'ingresso non proviene da un terminale"


14

Ho circa 10 php.inifile sul mio sistema, situati ovunque, e volevo sfogliarli rapidamente. Ho provato questo comando:

locate php.ini | xargs vi

Ma vimi avverte Input is not from a terminale poi la console inizia a diventare davvero strana - dopo di che devo premere :q!per uscire vie quindi disconnettermi dalla sessione ssh e riconnettermi per far funzionare normalmente la console.

Penso di aver capito cosa sta succedendo qui - fondamentalmente il comando non è terminato quando viavviato, quindi il comando forse non è finito e vinon pensa che il terminale sia in modalità normale.

Non ho idea di come risolverlo. Ho cercato Google e anche unix.stackexchange.com con sfortuna.



Come nota a margine, è possibile eseguire resetper ripristinare il terminale quando viene rovinato (non è necessario disconnettersi dalla sessione SSH).
wisbucky

Risposte:


12
vi $(locate php.ini)

Nota: questo avrà problemi se i percorsi dei file hanno spazi, ma è funzionalmente equivalente al tuo comando.
La prossima versione gestirà correttamente gli spazi ma è un po 'più complicata (le nuove righe nei nomi dei file comunque la interromperanno)

(IFS=$'\n'; vi $(locate php.ini))


Spiegazione:

Quello che sta succedendo è che i programmi ereditano i loro descrittori di file dal processo che li ha generati. xargsha il suo STDIN collegato allo STDOUT di locate, quindi vinon ha idea di cosa sia realmente lo STDIN originale.


2
xargs è meraviglioso, uno dei miei strumenti preferiti - non è adatto all'uso con programmi che usano stdin per qualsiasi cosa diversa da un feed di dati. mi piace la tua risposta e la tua spiegazione oltre a quella, quindi +1 comunque :)
cas

@CraigSanders Non mi piace perché è troppo facile abusare (usare impropriamente) e finire per rompersi. Non mi sono mai imbattuto in qualcosa che ho dovuto assolutamente usare xargsper quello che non poteva essere fatto direttamente con la shell (o find). Tuttavia, posso pensare ai casi in cui sarebbe la soluzione migliore. Quindi, fintanto che capisci cosa xargssta facendo, come divide gli argomenti, come esegue il programma, ecc., E lo stai usando correttamente, direi di provarlo :-P
Patrick

non può essere battuto per cose come ... | awk '{print $3}' | xargs | sed -e 's/ /+/g' | bc(sommare tutti i valori del campo 3). o con sed -e 's/ /|/g'per costruire una regexp. e sì, come qualsiasi strumento, devi sapere come usarlo e quali sono i suoi limiti e avvertenze.
Cas

L' vi $(...)approccio ha anche un problema con i caratteri jolly in shell diverse da zsh.
Stéphane Chazelas, il

Si noti inoltre che con l' xargsapproccio accanto al problema degli spazi bianchi, anche i nomi di file con virgolette singole, virgolette doppie e barre rovesciate sono un problema.
Stéphane Chazelas, il

10

Questa domanda è stata precedentemente posta sul forum Super User .

Citando dalla risposta di @ Grawity su quella domanda:

Quando invochi 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.)

Vim si aspetta che il suo stdin sia lo stesso del suo terminale di controllo ed esegue direttamente vari ioctl relativi al terminale su stdin. Se fatto su / dev / null (o qualsiasi descrittore di file non tty), questi ioctls sono privi di significato e restituiscono ENOTTY, che viene silenziosamente ignorato.

Questo è menzionato nelle pagine di manuale di xarg. Da OSX / BSD:

-o Riapri stdin come / dev / tty nel processo figlio prima di eseguire il comando. Ciò è utile se si desidera che xargs esegua un'applicazione interattiva.

Quindi, su OSX, è possibile utilizzare il seguente comando:

find . -name "php.ini" | xargs -o vim

Mentre, non esiste alcun interruttore diretto sulla versione GNU, questo comando funzionerà. (Assicurati di includere la dummystringa, altrimenti lascerà cadere il primo file.)

find . -name "php.ini" | xargs bash -c '</dev/tty vim "$@"' dummy

Le soluzioni di cui sopra sono state gentilmente concesse da Jaime McGuigan su SuperUser . Aggiungendoli qui per eventuali visitatori futuri alla ricerca del sito per questo errore.


3
+1 grazie per il suggerimento -o. uso xargs da anni e non ho mai notato che .... ho appena controllato la pagina man sul mio sistema, perché non è una funzionalità GNU xargs. La pagina man fornisce xargs sh -c 'emacs "$@" < /dev/tty' emacscome ciò che sostengono sia un'alternativa più flessibile e portatile (anche se è divertente per GNU preferire la portabilità alle funzionalità :).
Cas

2

Con GNU findutilse una shell con supporto per la sostituzione dei processi (ksh, zsh, bash), puoi fare:

xargs -r0a <(locate -0 php.ini) vi

L'idea è di passare l'elenco dei file tramite uno -a filenameanziché uno stdin. Usando si -0assicura che funzioni indipendentemente da quali caratteri o non caratteri possono contenere i nomi dei file.

Con zsh, potresti fare:

vi ${(0)"$(locate -0 php.ini)"}

(dove si 0trova il flag di espansione dei parametri da dividere su NUL).

Tuttavia, notare che, al contrario, xargs -rviene comunque eseguito visenza argomenti se non viene trovato alcun file.


0

Modifica più php.ini nello stesso editor?

Provare: vim -o $(locate php.ini)


0

Questo errore si verifica quando viene invocato vim ed è collegato all'output della pipeline precedente, anziché al terminale e riceve diversi input imprevisti (come NUL). Lo stesso accade quando si esegue:, vim < /dev/nullquindi il resetcomando in questo caso aiuta. Ciò è spiegato bene dalla gravità di superutente .

Su Unix / OSX puoi usare xargscon -oparametri, come:

locate php.ini | xargs -o vim

-oRiaprire stdin come / dev / tty nel processo figlio prima di eseguire il comando. Ciò è utile se si desidera che xargs esegua un'applicazione interattiva.

Su Linux prova la seguente soluzione alternativa:

locate php.ini | xargs -J% sh -c 'vim < /dev/tty $@'

In alternativa, usa GNU parallelinvece di xargsforzare l'allocazione di tty, ad esempio:

locate php.ini | parallel -X --tty vi

Nota: parallelsu Unix / OSX non funzionerà in quanto ha parametri diversi e non supporta tty.

Molti altri comandi popolari forniscono anche un'allocazione pseudo-tty (come -tin ssh), quindi cerca aiuto.

In alternativa, utilizzare findper passare i nomi dei file da modificare, quindi non è necessario xargs, basta usare -exec, ad esempio:

find /etc -name php.ini -exec vim {} +

0

L' IFShack di @ Patrick è necessario solo per conchiglie stupide come bashe zsh. fish divide la stringa su newline per impostazione predefinita.

$ vim (locate php.ini)

E Dio ci aiuti tutti se uno solo di noi ha effettivamente un file con una nuova riga nel suo nome. Dopo 17 anni usando Linux, non l'ho visto nemmeno una volta. Mi preoccuperei solo di supportare nomi di file con nuove righe per gli script che devono funzionare indipendentemente da ciò, ma gli script del genere probabilmente non eseguono VIM in modo interattivo.


zshsi divide su SPC, TAB, NL e NUL per impostazione predefinita. La cosa che non fa rispetto a bashè eseguire il globbing sul risultato in modo che i caratteri jolly nei nomi dei file non siano un problema. In zsh, lo faresti IFS=$'\0'; vi $(locate -0 php.ini)o come ho mostrato nella mia risposta vi ${(0)"$(locate -0 php.ini)"}per un operatore di suddivisione esplicita. Da notare anche il tcshvi "`locate php.ini`"
Stéphane Chazelas, il

Aw, merda. OK, questo funziona: $ f='not there'<ret>$ ls $f<ret>ma questo no: ls echo not there. OK sembra che sia necessario aggiornarlo un po '.
enigmatico

Sì, zsh non fa la cosa giusta quando lo fai ls "$(echo test; echo other test)". Solo il pesce fa la cosa giusta.
enigmatico

Supponendo che tu abbia significato lo stesso senza le virgolette, non è "giusto", si divide su linee, è solo una scelta diversa. zsh si divide in parole per impostazione predefinita (come tutte le altre shell) e può essere detto di dividere su linee o su NUL, tramite $IFSo tramite operatori espliciti ( fe 0flag di espansione dei parametri). Per nomi di file arbitrari, dividere per parola o dividere per linea è ugualmente sbagliato , è necessario dividere su NUL o analizzare una codifica, il che fishnon può essere fatto. In zsh, cioè IFS=$'\0'; ls -ld -- $(printf '%s\0' "$file1" "$file2")ols -ld -- ${(0)"$(printf '%s\0' "$file1" "$file2")"}
Stéphane Chazelas il

Meh. Dividere su newline è abbastanza buono. Come dice la risposta, le nuove righe nei nomi dei file sono estremamente rare. Non l'ho mai visto accadere letteralmente in 17 anni. E le newline sono separatori molto più convenienti delle null.
enigmatico

0

Un modo rapido per farlo, supponendo che si può garantire nessuno dei percorsi di file contengono SPC, TAB, NL, *, ?, [i personaggi (anche \e {...}in alcune shell) è quello di utilizzare back-tick (aka accenti gravi) per eseguire un comando prima un altro comando in esecuzione.

Per esempio

vi `find / -type f -name 'php.ini'`

Il comando contenuto tra i back-tick verrà eseguito per primo. L'output del comando contenuto viene quindi eseguito dal comando indicato prima dei back-tick.

Ad esempio, nella riga sopra, il find / -type f -name 'php.ini'comando eseguirà prima, invia output e quindi viverrà eseguito sul risultato di split + glob applicato a quell'output.


3
i segni di spunta sono troppo facilmente confusi per le virgolette singole. usa $(find ...)invece.
Cas

1
indovinare questo si spezzerà anche su spazi e / o newline nei nomi dei file?
Cwd

Questo è il modo in cui esegui i comandi di shell negli script bash. Non ho mai avuto interruzioni su spazi o nuove righe nei miei script o quando lo uso in una riga. Tuttavia, non ho mai provato ad aprire più file viusando questo metodo. È del tutto possibile che si possa rompere su nuove linee o spazi, a seconda di come vista leggendo ed eseguendo l'output.
tacotuesday
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.