Questão Invocando vi por find | xargs quebra meu terminal. Por quê?


Quando invocar vim através find | xargs, como isso:

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

você recebe um aviso sobre

Input is not from a terminal

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


119


origem


Nota lateral: Você pode executar esta operação inteiramente dentro do vim, não usando find ou xargs em absoluto. Abra o vim sem argumentos e execute :args **/*.txt<CR>para definir os argumentos do vim dentro do editor. - Trevor Powell
@ TrevorPowell: Em todos esses anos, o vim nunca deixou de me surpreender. - DevSolar
Relacionado: grep -l .. | xargs vim gera um aviso, por quê? no unix SE - kenorb
Relacionado: Terminal borked após invocar Vim com xargs no Vim SE. - kenorb
Relatório de erros do GitHub: O vim não manipula STDIN definido como / dev / null. - kenorb


Respostas:


Quando você invoca um programa via xargs, stdin do programa (entrada padrão) aponta para /dev/null. (Já que os xargs não conhecem o original stdin, 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 /

A Vim espera que seu stdin seja o mesmo que seu terminal de controle, e executa vários ioctlestá no stdin diretamente. Quando feito em /dev/null (ou qualquer outro descritor de arquivo não-tty), esses ioctls são insignificantes e retornam ENOTTY, que é silenciosamente ignorado.

  • Meu palpite em uma causa mais específica: Na inicialização, o Vim lê e lembra as configurações antigas do terminal e as restaura de volta ao sair. 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 correndo stty, que produzirá um monte de <undef>s. No Linux, rodando stty sane tornará o terminal utilizável novamente (embora vai perderam opções como iutf8, possivelmente causando pequenos aborrecimentos mais tarde).

Você poderia considerar isso um bug no Vim, já que posso aberto /dev/tty para controle de terminal, mas não faz. (Em algum momento durante a inicialização, o Vim duplica seu stderr para stdin, o que permite que ele leia seus comandos de entrada - de um fd aberto para gravação - mas mesmo isso não é feito cedo o suficiente.)


81



... com um floreio. Muito obrigado! - DevSolar
+1, e para TL, as pessoas de DR acabam de executar stty sane - rahmanisback
@rahmanisback: As outras respostas, além do comentário de Trevor, forneceram maneiras de evitar a quebra do terminal. Eu aceitei a resposta de Grawity, porque minha pergunta era "por que", não "como evitar" - isso é coberto por outra pergunta que na verdade desovada este. - DevSolar
@DevSolar Entendido, mas pense em pessoas frustradas como eu, que apenas pesquisam como se livrar desse comportamento, enquanto não - infelizmente - têm tempo suficiente agora para estudar "por que", o que é muito interessante mesmo assim. - rahmanisback
quando meu terminal quebra, assim, eu uso reset ao invés de stty sane e funciona bem depois disso. - Capi Etheriel


Na sequência da resposta do grawity, xargs pontos stdin para /dev/null


Do OSX / BSD man xargs

-o Reabrir stdin como / dev / tty no processo filho
        antes de executar o comando. Isso é útil
        se você quiser que os xargs executem um aplicativo interativo.

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

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

Para o GNU man xargs não há sinalização, mas podemos explicitamente passar em / dev / tty para resolver o problema:

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

o ignoreme está lá para pegar $ 0, então $ @ é todos os argumentos do xargs


117



Como você criaria um alias bash fora disso? $@ não parece estar traduzindo argumentos corretamente. - zanegray
@ zanegray - você não pode criar um alias, mas pode torná-lo uma função. Experimentar: function vimin () { xargs sh -c 'vim "$@" < /dev/tty' vim; } - Christopher


A maneira mais fácil:

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

28



A questão principal era "por que", não "como evitá-lo", e foi respondida à satisfação há dois anos e meio. - DevSolar
Isso, é claro, não funciona corretamente quando os nomes de arquivos contêm espaços ou outros caracteres especiais e também é um risco de segurança. - Dejay Clayton
Minha resposta favorita porque funciona para todos os comandos que listam arquivos, não apenas "localizar" ou curingas. Isso requer um pouco de confiança, como Dejay ressalta. - Travis Wilson
Isso não funcionará com muitos casos de uso que o xargs foi projetado para: por exemplo, quando o número de caminhos é muito alto (cc @TravisWilson) - Good Person


Deve funcionar muito bem se você usar a opção -exec no find ao invés de canalizar em xargs. Por exemplo

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


19



Huh ... o truque é o + (em vez de "o habitual" \;) para obter todos os arquivos encontrados 1 Sessão Vim - uma opção I guarda esquecendo. Você está certo, é claro, e +1 para isso. eu uso vim $(find ...) simplesmente por hábito. No entanto, eu estava realmente pedindo porque a operação do cano atrapalha o terminal, e a carranca acertou isso com sua explicação. - DevSolar
Esta é a melhor resposta e funciona em ambos os BSD / OSX / GNU / Linux. - kevinarpe
Além disso, o find não é a única maneira de obter uma lista de arquivos que devem ser editados simultaneamente pelo vim. Eu posso usar o grep para encontrar todos os arquivos com um padrão e tentar editá-los ao mesmo tempo. - Chandranshu


Use o GNU Parallel em vez disso:

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

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

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

Ele 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


8



Não disponível de forma onipresente. A maior parte do dia estou trabalhando em servidores onde não tenho a liberdade de instalar ferramentas adicionais. Mas obrigado pela dica de qualquer maneira. - DevSolar
Se você tiver liberdade para fazer o arquivo 'cat>; chmod + x file 'então você pode instalar o GNU Parallel: É simplesmente um script perl. Se você quiser man pages e tal, você pode instalá-lo sob o seu homedir: ./configure --prefix = $ HOME && make && make install - Ole Tange
OK, tentei isso - mas paralelo faz não abra todos os arquivos, abra-os em sucessão. Também é um bom bocado para uma operação simples. vim $(find . -name "*.txt") é mais simples e você obtém todos os arquivos abertos de uma só vez. - DevSolar
@DevSolar: Algo não relacionado, mas ambos find | xargs e $(find) terá grandes problemas com espaços em nomes de arquivos. - grawity
@grawity Correto, mas não há maneira fácil de contornar isso (que eu saiba). Você teria que começar a mexer com $IFS, -print0 e outras coisas, e então você deixou o reino de uma solução de linha de comando única e chegou a um ponto em que você deveria criar um script ... há uma razão pela qual os espaços nos nomes dos arquivos são desencorajados. - DevSolar