xargs e vi - “A entrada não é de um terminal”


14

Eu tenho cerca de 10 php.iniarquivos no meu sistema, localizados em todo o lugar, e queria procurá-los rapidamente. Eu tentei este comando:

locate php.ini | xargs vi

Mas vime avisa Input is not from a terminale, em seguida, o console começa a ficar realmente estranho - após o qual eu preciso pressionar :q!para sair vie desconectar da sessão ssh e reconectar para que o console se comporte normalmente novamente.

Eu acho que meio que entendo o que está acontecendo aqui - basicamente, o comando não terminou quando viiniciado, portanto o comando talvez não tenha terminado e vinão pense que o terminal esteja no modo normal.

Não faço ideia de como consertar. Eu pesquisei no Google e também no unix.stackexchange.com com pouca sorte.



Como uma observação lateral, você pode executar o resetreset do seu terminal quando ele estragar (você não precisa desconectar da sessão ssh).
wisbucky

Respostas:


12
vi $(locate php.ini)

Nota: isso terá problemas se os caminhos do arquivo tiverem espaços, mas é funcionalmente equivalente ao seu comando.
Esta próxima versão manipulará corretamente os espaços, mas é um pouco mais complicada (as novas linhas nos nomes de arquivos ainda serão quebradas)

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


Explicação:

O que está acontecendo é que os programas herdam seus descritores de arquivos do processo que os gerou. xargstem seu STDIN conectado ao STDOUT de locate, portanto vi, não tem idéia do que o STDIN original realmente está.


2
O xargs é maravilhoso, uma das minhas ferramentas favoritas - ele não é adequado para uso com programas que usam stdin para algo que não seja um feed de dados. Eu gosto da sua resposta e da sua explicação além disso, então +1 de qualquer maneira :) #
318

@CraigSanders Eu não gosto disso, porque é muito fácil abusar (usar indevidamente) e acabar quebrando. Nunca encontrei nada que eu absolutamente tivesse que usar, xargspois isso não poderia ser feito diretamente com o shell (ou find). No entanto, posso pensar em casos em que seria a melhor solução. Assim, desde que você entenda o que xargsestá fazendo, como ele divide os argumentos, como ele executa o programa, etc, e estão usando-o corretamente, eu diria que ir para ela :-P
Patrick

não pode ser batido para coisas como ... | awk '{print $3}' | xargs | sed -e 's/ /+/g' | bc(somar todos os valores do campo 3). ou com sed -e 's/ /|/g'para construir uma regexp. e sim, como qualquer ferramenta, você precisa saber como usá-la e quais são suas limitações e advertências.
cas

A vi $(...)abordagem também tem um problema com caracteres curinga em outros shells zsh.
Stéphane Chazelas

Observe também que, com a xargsabordagem ao lado do problema de espaço em branco, nomes de arquivos com aspas simples, aspas duplas e barras invertidas também são um problema.
Stéphane Chazelas 25/01

10

Esta questão foi previamente perguntado sobre o usuário Super fórum.

Citando a resposta de @ grawity sobre essa pergunta:

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

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

Isso é mencionado nas páginas de manual do xarg. Do OSX / BSD:

-o Reabra stdin como / dev / tty no processo filho antes de executar o comando. Isso é útil se você deseja que o xargs execute um aplicativo interativo.

Portanto, no OSX, você pode usar o seguinte comando:

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

Embora não haja opção direta na versão GNU, este comando funcionará. (Certifique-se de incluir a dummysequência, caso contrário ela eliminará o primeiro arquivo.)

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

As soluções acima são cortesia de Jaime McGuigan no SuperUser . Adicionando-os aqui para futuros visitantes pesquisando no site por esse erro.


3
+1 obrigado pela dica -o. Eu uso xargs há anos e nunca notei isso ... apenas verifiquei a página de manual no meu sistema, isso é porque não é um recurso GNU xargs. A página de manual fornece xargs sh -c 'emacs "$@" < /dev/tty' emacscomo o que eles alegam ser uma alternativa mais flexível e portátil (embora seja meio engraçado o GNU preferir a portabilidade do que os recursos :).
cas

2

Com GNU findutils, e um escudo com suporte para substituição de processo (ksh, zsh, bash), você pode fazer:

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

A idéia é passar a lista de arquivos por meio de um -a filenamestdin. O uso -0garante que ele funcione independentemente dos caracteres ou não caracteres que os nomes dos arquivos podem conter.

Com zsh, você pode fazer:

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

(onde 0é o sinalizador de expansão de parâmetro a ser dividido em NULs).

No entanto, observe que, ao contrário xargs -rdisso, ainda é executado visem argumento se nenhum arquivo for encontrado.


0

Editar vários php.ini dentro do mesmo editor?

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


0

Este erro ocorre quando o vim é chamado e está conectado à saída do pipeline anterior, em vez do terminal e recebendo diferentes entradas inesperadas (como NULs). O mesmo acontece quando você executa :, vim < /dev/nullportanto, o resetcomando neste caso ajuda. Isso é bem explicado pelo grawity no superusuário .

No Unix / OSX você pode usar xargscom o -oparâmetro, como:

locate php.ini | xargs -o vim

-oReabra stdin como / dev / tty no processo filho antes de executar o comando. Isso é útil se você deseja que o xargs execute um aplicativo interativo.

No Linux, tente a seguinte solução alternativa:

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

Como alternativa, use o GNU em parallelvez de xargsforçar a alocação de tty, no exemplo:

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

Nota: parallelno Unix / OSX não funcionará, pois possui parâmetros diferentes e não suporta tty.

Muitos outros comandos populares também fornecem alocação pseudo-tty (como -tem ssh), portanto, procure ajuda.

Como alternativa, use findpara passar nomes de arquivos para editar; portanto, não precisa xargs, basta usar -exec, por exemplo:

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

0

@ O IFShack de Patrick é necessário apenas para conchas idiotas como bashe zsh. fish divide a string em novas linhas por padrão.

$ vim (locate php.ini)

E Deus ajude a todos se um único de nós realmente tiver um arquivo com uma nova linha em seu nome. Depois de 17 anos usando o Linux, eu não o vi nem uma vez. Eu só me incomodaria em dar suporte a nomes de arquivos com novas linhas para scripts que precisam funcionar, não importa o que aconteça, mas scripts como esse provavelmente não estão executando o vim interativamente.


zshdivide em SPC, TAB, NL e NUL por padrão. O que ele não faz em comparação bashé observar os resultados, para que os caracteres curinga nos nomes dos arquivos não sejam um problema. Em zsh, você faria IFS=$'\0'; vi $(locate -0 php.ini)ou como mostrei na minha resposta vi ${(0)"$(locate -0 php.ini)"}para um operador de divisão explícito. Observe também o tcshvi "`locate php.ini`"
Stéphane Chazelas 25/01

Ah, merda. OK, isso funciona: $ f='not there'<ret>$ ls $f<ret>mas isso não: ls echo not there. OK parece que eu preciso atualizar isso um pouco.
enigmaticPhysicist

Sim, o zsh não faz a coisa certa quando você faz ls "$(echo test; echo other test)". Somente o peixe faz a coisa certa.
enigmaticPhysicist

Supondo que você quis dizer o mesmo sem as aspas, isso não é "certo", é dividido em linhas, é apenas uma escolha diferente. O zsh divide as palavras por padrão (como todos os outros shells) e pode ser instruído a dividir em linhas ou em NULs, via $IFSou via operadores explícitos ( fe 0sinalizadores de expansão de parâmetros). Para nomes de arquivos arbitrários, dividir por palavra ou dividir por linha é igualmente errado , é necessário dividir em NUL ou analisar alguma codificação, o que fishnão pode ser feito. Em zsh, isso é IFS=$'\0'; ls -ld -- $(printf '%s\0' "$file1" "$file2")ouls -ld -- ${(0)"$(printf '%s\0' "$file1" "$file2")"}
Stéphane Chazelas

Meh. Dividir em novas linhas é bom o suficiente. Como a resposta diz, novas linhas nos nomes de arquivos são extremamente raras. Eu literalmente nunca vi isso acontecer em 17 anos. E as novas linhas são separadores muito mais convenientes do que os nuls.
enigmaticPhysicist

0

Uma maneira rápida de fazê-lo, supondo que você pode garantir nenhum dos caminhos de arquivos contêm SPC, TAB, NL, *, ?, [personagens (também \e {...}em algumas conchas) é usar back-carrapatos (aka acento grave) para executar um comando antes outro comando em execução.

Por exemplo

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

O comando contido nos back-ticks será executado primeiro. A saída do comando contido é então executada pelo comando indicado antes dos back-ticks.

Por exemplo, na linha acima, o find / -type f -name 'php.ini'comando será executado primeiro, enviará a saída e, em seguida vi, será executado no resultado da divisão + glob aplicada a essa saída.


3
os back-ticks são facilmente confundidos para aspas simples. use em $(find ...)vez disso.
cas

1
adivinhar isso também quebrará em espaços e / ou novas linhas nos nomes dos arquivos?
Cwd

É assim que você executa comandos shell no script bash. Eu nunca tive nada quebrado em espaços ou novas linhas nos meus scripts ou ao usá-lo em um liner. No entanto, nunca tentei abrir vários arquivos viusando esse método. É bem possível que ele possa quebrar em novas linhas ou espaços, dependendo de como viestá lendo e executando a saída.
tacotuesday
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.