Chamando vi através de find | xargs quebra meu terminal. Por quê?

137

Ao invocar vimatravés de find | xargs, como este:

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

você recebe um aviso sobre

Input is not from a terminal

e um terminal com comportamento praticamente quebrado depois. Por que é que?

DevSolar
fonte
11
Nota: Você pode executar esta operação inteiramente dentro vim, não usar findou xargsem tudo. Abra o vim sem argumentos e execute :args **/*.txt<CR>para definir os argumentos do vim de dentro do editor.
Trevor Powell
3
@TrevorPowell: Em todos esses anos, o vim nunca deixou de me surpreender.
DevSolar
Relacionado: grep -l .. | xargs vimgera um aviso, por quê? no unix SE
kenorb 14/02
Relacionado: O terminal disparou após chamar o Vim com xargs no Vim SE.
19415 kenorb
Relatório de bug do GitHub: o vim não manipula STDIN definido como / dev / null .
Kenorb

Respostas:

100

Quando você invoca um programa via xargs, o stdin do programa (entrada padrão) aponta para /dev/null. (Como o xargs não conhece o stdin original , ele faz a próxima melhor coisa.)

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

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

O Vim espera que seu stdin seja igual ao seu terminal de controle e execute vários ioctls relacionados ao terminal no stdin diretamente. Quando executados em /dev/null(ou em qualquer descritor de arquivo não tty), esses ioctls não fazem sentido e retornam ENOTTY, que é ignorado silenciosamente.

  • Meu palpite para uma causa mais específica: Na inicialização, o Vim lê e se lembra das configurações antigas do terminal e as restaura novamente quando sai. Em nossa situação, quando as "configurações antigas" são solicitadas para um não-tty fd (descritor de arquivo), o Vim recebe todos os valores vazios e todas as opções desabilitadas, e descuidadamente define o mesmo para o seu terminal.

    Você pode ver isso executando vim < /dev/null, saindo e executando stty, o que produzirá muitos <undef>s. No Linux, o corredor stty sanevai fazer a utilizável de terminal novamente (embora se perderam opções como iutf8, possivelmente causando pequenos aborrecimentos mais tarde).

Você pode considerar isso um bug no Vim, pois ele pode ser aberto /dev/ttypara controle de terminal, mas não. (Em algum momento durante a inicialização, o Vim duplica seu stderr para stdin, o que permite ler seus comandos de entrada - de um arquivo aberto para gravação - mas mesmo isso não é feito o suficiente.)

gravidade
fonte
20
+1 e para TL; as pessoas DR apenas executamstty sane
doc_id 11/02/2015
@rahmanisback: As outras respostas, além do comentário de Trevor, forneceram maneiras de evitar a quebra do terminal em primeiro lugar. Aceitei a resposta do grawity, porque minha pergunta era "por que", não "como evitar" - isso é coberto por outra pergunta que realmente gerou essa.
DevSolar 11/11
@DevSolar Entendeu, mas pense em pessoas frustradas como eu, que apenas pesquisam no Google como se livrar desse comportamento, embora não - infelizmente - tenham tempo suficiente agora para estudar o "porquê", o que é muito interessante.
DOC_ID
4
quando meu terminal quebra, assim, eu uso em resetvez de stty sanee funciona bem depois disso.
Capi Etheriel
137

(Seguindo a explicação de grawity, isso xargsaponta stdinpara /dev/null.)

A solução para esse problema é adicionar o -oparâmetro a xargs. De man xargs:

-o

      Reabra stdin como /dev/ttyno processo filho antes de executar o comando. Isso é útil se você deseja xargsexecutar um aplicativo interativo.

Portanto, a seguinte linha de código deve funcionar para você:

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

O GNU xargs suporta essa extensão desde algum lançamento em 2017 (com o nome da opção longa --open-tty).

Para versões mais antigas ou outras do xargs, você pode explicitamente passar /dev/ttypara resolver o problema:

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

( ignoremeExiste para receber $ 0, para que $ @ sejam todos argumentos de xargs.)

James McGuigan
fonte
2
Como você criaria um alias de bash com isso? $@parece não estar traduzindo argumentos corretamente.
Zanegray 31/08/2015
1
@zanegray - você não pode criar um alias, mas pode torná-lo uma função. Tente:function vimin () { xargs sh -c 'vim "$@" < /dev/tty' vim; }
Christopher
Para uma explicação detalhada de como a solução xargs GNU funciona, e porque você precisa o manequim ignoremecorda, ver vi.stackexchange.com/a/17813
wisbucky
@zanegray, você pode criar um pseudônimo. As aspas são complicadas. Veja a solução em vi.stackexchange.com/a/17813
wisbucky 2/11/18
The -J, -o, -P and -R options are non-standard FreeBSD extensions which may not be available on other operating systems.(Não estava disponível no macOS para mim porque instalei o xargs a partir do homebrew (o GNU))
localhostdotdev
33

A maneira mais fácil:

vim $(find . -name "*foo*")
trolol
fonte
5
A questão principal era "por que", não "como evitá-lo", e foi respondida com satisfação há dois anos e meio.
DevSolar
5
Obviamente, isso não funciona corretamente quando os nomes de arquivos contêm espaços ou outros caracteres especiais e também é um risco à segurança.
DeJay Clayton
1
Minha resposta favorita porque funciona para todos os comandos que listam arquivos, não apenas para "encontrar" ou curingas. Exige um pouco de confiança, como Dejay aponta.
Travis Wilson
1
Isto não irá trabalhar com muitos casos de uso xargs é projetado para: por exemplo, quando o número de caminhos é muito elevado (cc @TravisWilson)
Boa Pessoa
21

Deverá funcionar muito bem se você usar a opção -exec em find ao invés de canalizar para xargs.

find . -type f -name filename.txt -exec vi {} + 
Chris Wraith
fonte
2
Huh ... o truque é o +(em vez de "o habitual" \;) para colocar todos os arquivos encontrados em uma sessão do Vim - uma opção que eu continuo esquecendo. Você está certo, é claro, e +1 para isso. Eu uso vim $(find ...)simplesmente por hábito. No entanto, eu estava realmente perguntando por que a operação do tubo estraga o terminal e o grawity acertou em cheio com sua explicação.
DevSolar
2
Esta é a melhor resposta e funciona no BSD / OSX / GNU / Linux.
Kevinarpe
1
Além disso, o find não é a única maneira de obter uma lista de arquivos que precisam ser editados simultaneamente pelo vim. Posso usar o grep para encontrar todos os arquivos com um padrão e tentar editá-los ao mesmo tempo.
Chandranshu
8

Use o GNU Parallel:

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

Ou se você deseja abrir todos os arquivos de uma só vez:

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

Ele até lida corretamente com nomes de arquivos como:

My brother's 12" records.txt

Assista ao vídeo de introdução para saber mais: http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
fonte
1
Não onipresente. Na maior parte do dia, trabalho em servidores nos quais não tenho liberdade para instalar ferramentas adicionais. Mas obrigado pela dica de qualquer maneira.
DevSolar 16/09
Se você tem a liberdade de fazer o arquivo 'cat>; chmod + x file ', então você pode instalar o GNU Parallel: É simplesmente um script perl. Se você quiser páginas de manual e tal, você pode instalá-lo em seu diretório de usuário: ./configure --prefix = $ HOME && make && make install
Ole Tange
2
OK, tentei isso - mas o paralelo não abre todos os arquivos, mas abre-os sucessivamente . Também é um bocado para uma operação simples. vim $(find . -name "*.txt")é mais simples e você abre todos os arquivos de uma só vez.
DevSolar
5
@ DevSolar: um pouco não relacionado, mas ambos find | xargse $(find)terão grandes problemas com espaços nos nomes dos arquivos.
grawity
2
@rawity Correto, mas não há uma maneira fácil de contornar isso (que eu saiba). Você teria que começar a mexer com $IFS, -print0e outras coisas, e então você deixou o reino de uma solução de linha de comando one-shot e chegou a um ponto onde você deve vir acima com um script ... há uma razão pela qual os espaços em nomes de arquivos são desencorajados .
DevSolar 21/09
0

talvez não seja o melhor, mas aqui está o script que eu uso (nomeado vim-open):

#!/usr/bin/env ruby

require 'shellwords'

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

irá trabalhar com vim-open a b ce ls | vim-openpor exemplo

localhostdotdev
fonte
Quanto a várias outras respostas, observe que a pergunta real era "por que", não "como evitá-la". (Por que eu ainda apontam para o comentário de Trevor sob a minha pergunta como a forma mais sólida que não necessita de script, aliases ou qualquer coisa.)
DevSolar