bash – captura automaticamente a saída do último comando executado em uma variável

Eu gostaria de poder usar o resultado do último comando executado em um comando subseqüente. Por exemplo,

$ find . -name foo.txt ./home/user/some/directory/foo.txt 

Agora, digamos que eu queira poder abrir o arquivo em um editor, excluí-lo ou fazer outra coisa com ele, por exemplo

 mv  /some/new/location 

Como eu posso fazer isso? Talvez usando alguma variável bash?

Atualizar:

Para esclarecer, não quero atribuir as coisas manualmente. O que eu estou procurando é algo como variables ​​básicas do bash, por exemplo

 ls /tmp cd $_ 

$_ contém o último argumento do comando anterior. Eu quero algo semelhante, mas com a saída do último comando.

Atualização final:

A resposta de Seth funcionou muito bem. Algumas coisas para ter em mente:

  • não se esqueça de touch /tmp/x ao tentar a solução pela primeira vez
  • o resultado será armazenado apenas se o código de saída do último comando for bem-sucedido

Esta é uma solução realmente hacky, mas parece funcionar na maior parte do tempo. Durante os testes, notei que às vezes não funcionava muito bem ao obter um ^ C na linha de comando, embora eu tenha ajustado um pouco para me comportar um pouco melhor.

Este hack é um hack de modo interativo apenas, e estou bastante confiante de que não recomendaria a ninguém. Comandos em segundo plano provavelmente causarão um comportamento ainda menos definido que o normal. As outras respostas são uma maneira melhor de obter resultados de forma programática.


Dito isto, aqui está a “solução”:

 PROMPT_COMMAND='LAST="`cat /tmp/x`"; exec >/dev/tty; exec > >(tee /tmp/x)' 

Defina esta variável ambiental bash e emite comandos conforme desejado. $LAST normalmente terá a saída que você está procurando:

 startide seth> fortune Courtship to marriage, as a very witty prologue to a very dull play. -- William Congreve startide seth> echo "$LAST" Courtship to marriage, as a very witty prologue to a very dull play. -- William Congreve 

Não conheço nenhuma variável que faça isso automaticamente . Para fazer algo além de apenas copiar e colar o resultado, você pode executar novamente o que acabou de fazer, por exemplo

 vim $(!!) 

Onde !! é expansão de história que significa ‘o comando anterior’.

Se você espera que haja um único nome de arquivo com espaços ou outros caracteres que possam impedir a análise apropriada do argumento, cite o resultado ( vim "$(!!)" ). Deixá-lo sem aspas permitirá que vários arquivos sejam abertos de uma só vez, desde que não incluam espaços ou outros tokens de análise de shell.

Bash é uma espécie de linguagem feia. Sim, você pode atribuir a saída à variável

 MY_VAR="$(find -name foo.txt)" echo "$MY_VAR" 

Mas é melhor esperar que o mais difícil que find só retorne um resultado e que esse resultado não tenha nenhum caractere “estranho”, como retornos de linha ou feeds de linha, pois eles serão modificados silenciosamente quando atribuídos a uma variável Bash.

Mas é melhor ter cuidado ao citar sua variável corretamente ao usá-la!

É melhor agir diretamente no arquivo, por exemplo, com find -execdir (consulte o manual).

 find -name foo.txt -execdir vim '{}' ';' 

ou

 find -name foo.txt -execdir rename 's/\.txt$/.xml/' '{}' ';' 

Existem mais de uma maneira de fazer isso. Uma maneira é usar v=$(command) que irá atribuir a saída do comando para v . Por exemplo:

 v=$(date) echo $v 

E você também pode usar backquotes.

 v=`date` echo $v 

De Bash Beginners Guide ,

Quando a forma de substituição de estilo antigo backquoted é usada, a barra invertida mantém seu significado literal, exceto quando seguida por “$”, “` “ou” \ “. Os primeiros backticks não precedidos por uma barra invertida terminam a substituição do comando. Ao usar o formulário “$ (COMMAND)”, todos os caracteres entre parênteses compõem o comando; nenhum é tratado especialmente.

EDIT: Após a edição da pergunta, parece que isso não é a coisa que o OP está procurando. Tanto quanto eu sei, não há nenhuma variável especial como $_ para a saída do último comando.

É muito fácil. Use aspas traseiras:

 var=`find . -name foo.txt` 

E então você pode usar isso a qualquer momento no futuro

 echo $var mv $var /somewhere 

Eu acho que você pode ser capaz de cortar uma solução que envolve a configuração do seu shell para um script contendo:

 #!/bin/sh bash | tee /var/log/bash.out.log 

Então, se você definir $PROMPT_COMMAND para $PROMPT_COMMAND um delimitador, você pode escrever uma function auxiliar (talvez chamada _ ) que lhe dará o último pedaço desse log, para que você possa usá-lo como:

 % find lots*of*files ... % echo "$(_)" ... # same output, but doesn't run the command again 

Disclamers:

  • Esta resposta é final de meio ano: D
  • Sou um usuário tmux pesado
  • Você tem que rodar seu shell no tmux para que isso funcione

Ao executar um shell interativo no tmux, você pode acessar facilmente os dados atualmente exibidos em um terminal. Vamos dar uma olhada em alguns comandos interessantes:

  • tmux capture-pane : este copia os dados exibidos para um dos buffers internos do tmux. Pode copiar o histórico que atualmente não é visível, mas não estamos interessados ​​nisso agora
  • buffers de lista do tmux : exibe as informações sobre os buffers capturados. O mais novo terá o número 0.
  • tmux show-buffer -b (num buffer) : isso imprime o conteúdo do buffer dado em um terminal
  • tmux paste-buffer -b (num buffer) : isso cola o conteúdo do buffer dado como input

Sim, isso nos dá muitas possibilidades agora 🙂 Quanto a mim, eu configurei um alias simples: alias L="tmux capture-pane; tmux showb -b 0 | tail -n 3 | head -n 1" e agora Toda vez que eu preciso acessar a última linha eu simplesmente uso $(L) para obtê-lo.

Isso é independente do stream de saída que o programa usa (seja stdin ou stderr), do método de impressão (ncurses, etc.) e do código de saída do programa – os dados precisam ser exibidos.

Você pode configurar o seguinte alias no seu perfil bash:

 alias s='it=$($(history | tail -2 | head -1 | cut -d" " -f4-))' 

Então, digitando ‘s’ depois de um comando arbitrário, você pode salvar o resultado em uma variável de shell ‘it’.

Então, o uso de exemplo seria:

 $ which python /usr/bin/python $ s $ file $it /usr/bin/python: symbolic link to `python2.6' 

Eu acabei de destilar esta function bash das sugestões aqui:

 grab() { grab=$("$@") echo $grab } 

Então você acabou de fazer:

 > grab date Do 16. Feb 13:05:04 CET 2012 > echo $grab Do 16. Feb 13:05:04 CET 2012 

Atualização : um usuário anônimo sugeriu replace echo por printf '%s\n' que tem a vantagem de não processar opções como -e no texto capturado. Então, se você espera ou experimenta tais peculiaridades, considere esta sugestão. Outra opção é usar cat < <<$grab lugar.

Capture a saída com backticks:

 output=`program arguments` echo $output emacs $output 

Eu costumo fazer o que os outros aqui sugeriram … sem a tarefa:

 $find . -iname '*.cpp' -print ./foo.cpp ./bar.cpp $vi `!!` 2 files to edit 

Você pode ficar mais chique se quiser:

 $grep -R "some variable" * | grep -v tags ./foo/bar/xxx ./bar/foo/yyy $vi `!!` 

Ao dizer “Eu gostaria de poder usar o resultado do último comando executado em um comando subseqüente”, eu assumo – você quer dizer o resultado de qualquer comando, não apenas encontrar.

Se isso é o caso – xargs é o que você está procurando.

find . -name foo.txt -print0 | xargs -0 -I{} mv {} /some/new/location/{}

OU se você estiver interessado em ver a saída primeiro:

find . -name foo.txt -print0

!! | xargs -0 -I{} mv {} /some/new/location/{}

Esse comando lida com vários arquivos e funciona como um encanto, mesmo que o caminho e / ou o nome do arquivo contenham espaço (s).

Observe a parte mv {} / some / new / location / {} do comando. Este comando é construído e executado para cada linha impressa pelo comando anterior. Aqui, a linha impressa pelo comando anterior é substituída no lugar de {} .

Trecho da página man do xargs:

xargs – constrói e executa linhas de comando a partir da input padrão

Para mais detalhes, veja man page: man xargs

Se tudo que você quer é executar novamente seu último comando e obter a saída, uma variável bash simples funcionaria:

 LAST=`!!` 

Então você pode executar o seu comando na saída com:

 yourCommand $LAST 

Isso gerará um novo processo e executará novamente o comando, fornecendo a saída. Parece que você realmente gostaria de ser um arquivo de histórico bash para saída de comando. Isso significa que você precisará capturar a saída que o bash envia para o seu terminal. Você poderia escrever algo para assistir o / dev ou / proc necessário, mas isso é confuso. Você também pode simplesmente criar um “pipe especial” entre seu termo e o bash com um comando tee no meio que redireciona para o seu arquivo de saída.

Mas ambos são soluções hacky. Eu acho que a melhor coisa seria terminator, que é um terminal mais moderno com logging de saída. Apenas verifique seu arquivo de log para obter os resultados do último comando. Uma variável bash similar à acima tornaria isso ainda mais simples.

Aqui está uma maneira de fazer isso depois de executar o comando e decidir que você deseja armazenar o resultado em uma variável:

 $ find . -name foo.txt ./home/user/some/directory/foo.txt $ OUTPUT=`!!` $ echo $OUTPUT ./home/user/some/directory/foo.txt $ mv $OUTPUT somewhere/else/ 

Ou se você souber antecipadamente que deseja o resultado em uma variável, poderá usar os backticks:

 $ OUTPUT=`find . -name foo.txt` $ echo $OUTPUT ./home/user/some/directory/foo.txt 

Como alternativa às respostas existentes: Use while se seus nomes de arquivo puderem conter espaços em branco como este:

 find . -name foo.txt | while IFS= read -r var; do echo "$var" done 

Como escrevi, a diferença só é relevante se você tiver que esperar espaços em branco nos nomes dos arquivos.

NB: o único material interno não é sobre a saída, mas sobre o status do último comando.

você pode usar !!: 1. Exemplo:

 ~]$ ls *.~ class1.cpp~ class1.h~ main.cpp~ CMakeList.txt~ ~]$ rm !!:1 rm class1.cpp~ class1.h~ main.cpp~ CMakeList.txt~ ~]$ ls file_to_remove1 file_to_remove2 file_to_remove1 file_to_remove2 ~]$ rm !!:1 rm file_to_remove1 ~]$ rm !!:2 rm file_to_remove2 

Eu tive uma necessidade semelhante, em que eu queria usar a saída do último comando para o próximo. Muito parecido com um | (tubo). por exemplo

 $ which gradle /usr/bin/gradle $ ls -alrt /usr/bin/gradle 

para algo como –

 $ which gradle |: ls -altr {} 

Solução: criou este pipe personalizado. Muito simples, usando xargs –

 $ alias :='xargs -I{}' 

Basicamente nada, criando uma mão curta para xargs, funciona como charme e é muito útil. Acabei de adicionar o alias no arquivo .bash_profile.

Isso não é estritamente uma solução bash, mas você pode usar o piping com sed para obter a última linha de saída dos comandos anteriores.

Primeiro vamos ver o que eu tenho na pasta “a”

 rasjani@helruo-dhcp022206::~$ find a a a/foo a/bar a/bat a/baz rasjani@helruo-dhcp022206::~$ 

Então, o seu exemplo com ls e cd se transformaria em sed e piping em algo parecido com isto:

 rasjani@helruo-dhcp022206::~$ cd `find a |sed '$!d'` rasjani@helruo-dhcp022206::~/a/baz$ pwd /home/rasjani/a/baz rasjani@helruo-dhcp022206::~/a/baz$ 

Então, a mágica real acontece com o sed, você canaliza a saída do comando ever em sed e sed, imprimindo a última linha que você pode usar como parâmetro com back carrapatos. Ou você pode combinar isso com xargs também. (“man xargs” no shell é seu amigo)

O shell não possui símbolos especiais semelhantes a perl que armazenam o resultado do eco do último comando.

Aprenda a usar o símbolo pipe com awk.

 find . | awk '{ print "FILE:" $0 }' 

No exemplo acima, você poderia fazer:

 find . -name "foo.txt" | awk '{ print "mv "$0" ~/bar/" | "sh" }' 
 find . -name foo.txt 1> tmpfile && mv `cat tmpfile` /path/to/some/dir/ 

ainda é outra maneira, embora suja.