Como posso executar a última linha de saída no bash?

12

Eu sei que posso executar o último comando no bash, com !!, mas como posso executar a última linha de saída?

Estou pensando no caso de uso desta saída:

The program 'git' is currently not installed. You can install it by typing:
sudo apt-get install git

Mas não sei como posso fazer isso. Estou pensando em algo como o !!, talvez @@ou similar?


Superusuário também tem essa pergunta.

Tim
fonte
1
Por que você não copia e cola?
Pilot6
1
@ Tim Você quer maneiras de executar a última linha da saída de um programa, mas para este caso de uso específico, há também a abordagem de alterar a command_not_found_handlefunção shell ou um script que ele executa (geralmente apenas /usr/lib/command-not-found). Eu acho que você poderia fazê-lo para que você seja solicitado interativamente com a opção de instalar automaticamente o pacote, ou (provavelmente melhor) para que o nome do pacote seja armazenado em algum lugar e possa ser instalado executando um pequeno comando. Isso é diferente o suficiente do que você pediu aqui. Você pode postar uma pergunta separada sobre isso.
Eliah Kagan
2
Pode também ser relevante: github.com/nvbn/thefuck (Ignore o nome) esta auto ferramenta corrige git / apt / etc comandos :)
bhathiya-perera

Respostas:

16

O comando $(!! |& tail -1)deve fazer:

$ git something
The program 'git' is currently not installed. You can install it by typing:
sudo apt-get install git

$ $(!! |& tail -1)
$(git something |& tail -1)
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
git-man liberror-perl

Como você pode ver, o sudo apt-get install gitcomando está sendo executado.

EDIT: Quebrando$(!! |& tail -1)

  • $()é o bashpadrão de substituição de comando

  • bashexpandirá !!para o último comando executado

  • |&parte é complicada. Normalmente, o pipe |pega o STDOUT do comando do lado esquerdo e o passa como STDIN para o comando no lado direito do |, no seu caso, o comando anterior imprime sua saída no STDERR como uma mensagem de erro. Portanto, fazer |não ajudaria, precisamos passar o STDOUT e o STDERR (ou apenas STDERR) para o comando do lado direito. |&passará STDOUT e STDERR como STDIN para o comando do lado direito. Como alternativa, melhor você só pode passar o STDERR:

    $(!! 2>&1 >/dev/null | tail -1)
  • tail -1como sempre, imprimirá a última linha a partir de sua entrada. Aqui, em vez de imprimir a última linha, você pode ser mais preciso, como imprimir a linha que contém o apt-get installcomando:

    $(!! 2>&1 >/dev/null | grep 'apt-get install')
heemail
fonte
1
Sua abordagem assume que a saída será idêntica na segunda vez. Alguns comandos podem produzir uma saída diferente na próxima vez. Nesse caso, é muito difícil prever o que a execução da última linha de sua saída realmente fará.
Kasperd # 10/15
1
@kasperd Você está right..althouh na medida em que este exemplo específico está em causa, a saída deve permanecer o mesmo ..
heemayl
7

TL; DR: alias @@='$($(fc -ln -1) |& tail -1)'

As instalações de interação do histórico do Bash não oferecem nenhum mecanismo para examinar a saída dos comandos. O shell não armazena isso , e a expansão do histórico é específica para os comandos que você mesmo executou ou para partes desses comandos.

Isso deixa a abordagem de executar novamente o último comando e canalizar stdout e stderr ( |&) em uma substituição de comando. A resposta de heemayl consegue isso, mas não pode ser usada em um alias porque o shell executa a expansão do histórico antes de expandir os aliases, e não depois.

Também não posso fazer com que a expansão do histórico funcione em uma função shell, mesmo ativando-a na função com set -H. Eu suspeito que !!uma função nunca será expandida, e não tenho certeza para o que seria expandida, mas, no momento, não sei exatamente por que não é.

Portanto, se você deseja configurar as coisas para que você possa fazer isso com muito pouca digitação, use o fcshell interno em vez da expansão do histórico para extrair o último comando do histórico. Isso tem a vantagem adicional de funcionar mesmo quando a expansão do histórico está desativada.

Como mostrado na Gordon Davisson 's resposta para criar um apelido contendo expansão história do bash (em Super User ), $(fc -ln -1)Simula !!. Ligar o dentro para !!no comando do heemayl $(!! |& tail -1) rendimentos:

$($(fc -ln -1) |& tail -1)

Isso funciona como, $(!! |& tail -1)mas pode ter uma definição de alias:

alias @@='$($(fc -ln -1) |& tail -1)'

Depois de executar essa definição, colocá-la em .bash_aliasesou .bashrciniciar um novo shell, você pode simplesmente digitar @@(ou o que quer que tenha chamado de alias) para tentar executar a última linha de saída do último comando.

ek@Io:~$ alias @@='$($(fc -ln -1) |& tail -1)'
ek@Io:~$ evolution
The program 'evolution' is currently not installed. You can install it by typing:
sudo apt-get install evolution
ek@Io:~$ @@
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following extra packages will be installed:
  evolution-common evolution-data-server evolution-data-server-online-accounts
....
Eliah Kagan
fonte
Existe alguma opção para mover isso para um script separado? Eu tentei isso e finalmente falhei, porque o fc não produz nada ... Como o fc segue o histórico da sessão atual do shell, movê-lo para um script separado abre uma sessão diferente com o histórico vazio, é claro ... adicionei alguns comandos acima da linha fc no script, e um deles foi executado. Então, eu me pergunto se existe alguma opção para informar o script, que seu "contexto" é a sessão do bash, que o executou. Como, alguns argumentos para #! / Bin / bash ou algo assim ...!
Exterminator13
@ Exterminator13 Se você quer dizer um script separado que você executa - como ./script, não o que você fornece com .ou sourceembutido - não tenho certeza. O Bash anexa a um arquivo na saída do shell ou quando você executa historycom -aou -w(consulte help history). Se isso acontecesse, os comandos em vários shells interativos seriam intercalados (aparecendo na ordem em que você os executou). Você pode anexá-lo imediatamente. . Mas acho que há mais o que fazer para fctrabalhar em um script. Sugiro postar uma nova pergunta sobre isso. Se o fizer, sinta-se à vontade para comentar aqui com um link para ele.
Eliah Kagan 19/06/19
2

Se você estiver usando um emulador de terminal no X, como gnome-terminal, konsoleou xterm, poderá usar a seleção X para algo como copiar e colar:

Use a seleção principal

Selecione a linha inteira para executar com um clique esquerdo triplo .

Em seguida, cole-o na linha de comando usando o botão do meio .

Isso deve funcionar na maioria dos emuladores de terminal. Utiliza a seleção primária, sem tocar na área de transferência - elas são separadas.
É selecionar e, em seguida, combinar copiar e colar em uma operação, tecnicamente.

Volker Siegel
fonte
1
@ Tim Isso é verdade - eu vou deixar isso claro.
Volker Siegel
1

Como Eliah Kagan mencionou , o shell não armazena a saída do comando. No entanto, existe uma maneira de obter a última linha de saída do último comando, sem executar o comando novamente , se você executar a tela .

Coloque as seguintes linhas em você ~/.screenrc:

register t "^a[k0y$ ^a]^a^L"
bind t process t

Em seguida, você pode pressionar Ctrl+ atpara inserir a linha anterior no prompt atual. Seria possível fazer isso também pressionar automaticamente enter, mas provavelmente é uma idéia melhor verificá-lo antes de executar e pressione enter manualmente.

Como funciona:

  • register tregistra uma string em um buffer chamado t. A sequência a seguir é uma sequência de teclas.
  • bind tvincula à chave t(ou seja, executar usando Ctrl+ at), process tsignifica avaliar o conteúdo do buffer nomeado t.
  • A própria sequência significa:
    • ^a[(= Ctrla[): entra no modo de cópia
    • ksuba uma linha, 0vá para o início da linha, y$copie até o final da linha, (espaço) complete o comando
    • ^a](= Ctrla]): cole o conteúdo copiado
    • ^a^L(= CtrlaCtrlL) atualize a tela (caso contrário, o conteúdo colado não será exibido, porque você não o digitou
atextor
fonte