O zsh pode acessar o stdout do programa da última execução?

14

Costumo usar findou locatepara descobrir caminhos.

(~) locate foobar.mmpz
/home/progo/lmms/projects/foobar.mmpz

O próximo passo é frequentemente abrir ou manipular os arquivos. Em um caso feliz como acima, eu posso fazer isso:

(~) ls `!!`
ls `locate foobar.mmpz`
/home/progo/lmms/projects/foobar.mmpz

Mas ninguém fica muito feliz quando há muitas linhas de saída, algumas das quais podem não ser caminhos ou algo desse tipo. Além disso, reexecutar comandos potencialmente inúteis também não é tão elegante.

Haveria uma maneira de conectar o zsh para armazenar o stdout em uma matriz para manipulação posterior? Afinal, o trabalho do shell é redirecionar os fluxos para o usuário. Eu estou pensando que poderia armazenar a primeira N e a última N linhas em uma variável para uso imediato imediato, como $?e outras.

Ok, então isso é bem legal: /unix//a/59704/5674 . Agora estou perguntando sobre o know-how do zsh (e portando o código para o zsh) para manipular esse tipo de captura após cada linha de execução.

unperson325680
fonte
Não é realmente o trabalho do shell redirecionar o fluxo para o usuário. Os aplicativos gravam sua saída no dispositivo terminal, o shell não está envolvido nisso.
Stéphane Chazelas
@StephaneChazelas, mas o trabalho do shell é, por exemplo, redirecionar fluxos para arquivos por solicitação do usuário. Também existem receitas para o zsh que colorem stderr em vermelho para separá-lo do stdout. Eu acho que isso indica o papel do shell na transmissão.
Unperson325680
Sim, você pode fazê-lo usando os ganchos screenor scripte precmd e preexec.
Stéphane Chazelas 27/02

Respostas:

6

Não há recurso para capturar a saída da tela na maioria dos emuladores de terminal. Eu me lembro do autor do xterm (o emulador de terminal de "referência") afirmando que seria difícil de implementar. Mesmo se isso fosse possível, o shell teria que rastrear onde estava o último prompt.

Portanto, você não deixará de executar o comando novamente, a menos que use um mecanismo manual específico do terminal, como colar e colar com o mouse no xterm ou com o teclado na tela.

Seria altamente impraticável para o shell capturar automaticamente a saída dos comandos, porque não é possível distinguir entre comandos que possuem complexas interações entre terminal e usuário e comandos que simplesmente produzem caracteres imprimíveis.

Você pode executar novamente o comando e capturar sua saída. Existem várias maneiras de fazer cada uma. Para executar novamente o comando, você pode usar:

  • !! substituição de histórico - mais conveniente para digitar;
  • fc -e -, que pode ser usado em uma função

Para capturar a saída, você pode usar a substituição de comandos ou uma função como a seguinte:

K () {
  lines=("${(f@)$(cat)}")
}
!! |K

Isso define a linesmatriz para a saída do comando que é canalizado para ela.

Gilles 'SO- parar de ser mau'
fonte
Que assim seja. Agora, à luz de boas explicações, percebo que uma abordagem completamente automática é muito difícil, pois a segunda melhor coisa é como a Ksua.
usar o seguinte
3

Aqui está um primeiro corte de algo para colocar a última linha de saída em uma variável chamada $lastline.

precmd () {                                                                                                                                                                                                        
    exec 2>&- >&-
    lastline=$(tail -1 ~/.command.out) 
    sleep 0.1   # TODO: synchronize better
    exec > /dev/tty 2>&1
}

preexec() {
    exec > >(tee ~/.command.out&)
}

Isso usa o preexecgancho do zsh para executar exece teearmazena uma cópia do stdout do comando, depois usaprecmd para ler a saída armazenada e restaurar stdout para ser apenas o terminal para mostrar o prompt.

Mas ainda precisa de algum trabalho. Por exemplo, como stdout não é mais um terminal, programas como vimeless não funcionam corretamente.

Há algumas informações relacionadas úteis nessas perguntas:

Mikel
fonte
Sim, talvez uma abordagem 100% automática não funcione. Existem muitas execchamadas no código, o comando está sendo executado várias vezes ou estou preso em semântica especial?
Unperson325680
execsem um nome de programa apenas define redirecionamentos.
Mikel
2

Você pode fazer isso apenas direcionando seus resultados para tee , o que salva a saída em um arquivo e a exibe ao mesmo tempo.

Por exemplo, você pode fazer isso, que mostra sua saída normalmente, mas também a salva no arquivo /tmp/it

locate foobar.mmpz | tee /tmp/it

então cat que arquivo e grep-lo para selecionar as coisas, por exemplo

cat /tmp/it | grep progo | grep lms

então, para usá-lo, você pode fazer o seguinte:

vi $(!!)

Brad Parks
fonte
2

Eu vim com esta solução meia cozida:

alias -g ___='"$(eval "$(fc -ln -1)" | tail -n 1)"'

Isso permite que você escreva ___em qualquer ponto da linha de comando. O comando anterior será executado novamente e ___será substituído pela última linha de sua saída. Exemplo de uso:

$ touch foo bar baz
$ ls -1
bar
baz
foo
$ vim ___

O último comando será expandido para vim foo.

Isso tem algumas arestas afiadas!

  • Se você incluir ___um comando, mas o comando anterior também incluir um ___, o shell ficará travado em um estado estranho por um tempo. Você pode sair desse estado imediatamente com Ctrl- C.

  • Você também não pode pressionar Tabpara expandir o ___, como pode com !$e outras construções.

  • Alguns comandos exibem saídas diferentes quando são executados “normalmente” e quando são conectados a um tubo. (Compare a saída de lse ls | cat.) Se o comando acionado por ___for um deles, você poderá executar um comando diferente do esperado.

  • E, é claro, se você quiser fazer algo com uma linha de saída diferente da última, isso não ajudará.

Eu escolhi o nome ___porque é algo que eu nunca quis incluir como palavra em uma linha de comando, nem mesmo como argumento. Você pode escolher um nome diferente, mas tome cuidado para não escolher algo que possa ser expandido para você inadvertidamente.

Bdesham
fonte