bash: captura automáticamente la salida del último comando ejecutado en una variable

Me gustaría poder utilizar el resultado del último comando ejecutado en un comando posterior. Por ejemplo,

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

Ahora digamos que quiero poder abrir el archivo en un editor, o eliminarlo, o hacer algo más con él, por ejemplo

 mv  /some/new/location 

¿Cómo puedo hacerlo? ¿Tal vez usando alguna variable bash?

Actualizar:

Para aclarar, no quiero asignar cosas manualmente. Lo que busco es algo así como variables de bash integradas, por ejemplo

 ls /tmp cd $_ 

$_ contiene el último argumento del comando anterior. Quiero algo similar, pero con la salida del último comando.

Actualización final:

La respuesta de Seth ha funcionado bastante bien. Un par de cosas para tener en cuenta:

  • no olvide touch /tmp/x cuando intente la solución por primera vez
  • el resultado solo se almacenará si el código de salida del último comando fue exitoso

Esta es una solución realmente hacky, pero parece funcionar en gran medida algunas veces. Durante las pruebas, noté que a veces no funcionaba muy bien cuando obtenía un ^ C en la línea de comando, aunque lo modifiqué un poco para comportarme un poco mejor.

Este hack es solo un hack de modo interactivo, y estoy bastante seguro de que no se lo recomendaría a nadie. Es probable que los comandos de fondo causen un comportamiento aún menos definido que el normal. Las otras respuestas son una mejor forma de obtener resultados programáticamente.


Dicho esto, aquí está la “solución”:

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

Establezca esta variable de entorno bash y emita comandos según lo desee. $LAST generalmente tendrá el resultado que está buscando:

 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 

No sé de ninguna variable que haga esto automáticamente . Para hacer algo además de copiar y pegar el resultado, puede volver a ejecutar lo que acaba de hacer, por ejemplo

 vim $(!!) 

Donde !! es la expansión de la historia que significa ‘el comando anterior’.

Si espera que haya un solo nombre de archivo con espacios u otros caracteres que puedan impedir el análisis adecuado de los argumentos, cite el resultado ( vim "$(!!)" ). Dejarlo sin comillas permitirá que se abran archivos múltiples a la vez siempre que no incluyan espacios u otros tokens de análisis de shell.

Bash es una especie de lenguaje feo. Sí, puede asignar el resultado a variable

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

Pero mejor espero que sea más difícil que find solo un resultado y que ese resultado no contenga caracteres “impares”, como retornos de carro o alimentaciones de línea, ya que se modificarán silenciosamente cuando se asignen a una variable de Bash.

¡Pero mejor ten cuidado de citar tu variable correctamente cuando la uses!

Es mejor actuar en el archivo directamente, por ejemplo, con find ‘s -execdir (consulte el manual).

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

o

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

Hay más de una formas de hacer esto. Una forma es usar v=$(command) que asignará la salida del comando a v . Por ejemplo:

 v=$(date) echo $v 

Y también puedes usar las comillas inversas.

 v=`date` echo $v 

De la guía Bash para principiantes ,

Cuando se utiliza la forma antigua de sustitución con retroceso, la barra invertida conserva su significado literal, excepto cuando está seguida de “$”, “` “o” \ “. Los primeros backticks no precedidos por una barra invertida terminan la sustitución del comando. Al usar el formulario “$ (COMMAND)”, todos los caracteres entre paréntesis componen el comando; ninguno es tratado especialmente.

EDITAR: después de la edición en la pregunta, parece que esto no es lo que el OP está buscando. Hasta donde yo sé, no hay una variable especial como $_ para la salida del último comando.

Es bastante fácil. Use citas retrospectivas

 var=`find . -name foo.txt` 

Y luego puedes usar eso en cualquier momento en el futuro

 echo $var mv $var /somewhere 

Creo que es posible que pueda descifrar una solución que implica configurar su shell a un script que contenga:

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

Luego, si configura $PROMPT_COMMAND para generar un delimitador, puede escribir una función auxiliar (quizás llamada _ ) que le proporcione el último fragmento de ese registro, para que pueda usarlo como sigue:

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

Descargos:

  • Esta respuesta es tardía medio año: D
  • Soy un usuario pesado de tmux
  • Tienes que ejecutar tu shell en tmux para que esto funcione

Cuando se ejecuta un shell interactivo en tmux, puede acceder fácilmente a los datos que se muestran actualmente en un terminal. Echemos un vistazo a algunos comandos interesantes:

  • tmux capture-pane : este copia los datos mostrados en uno de los buffers internos de tmux. Puede copiar el historial que actualmente no está visible, pero no estamos interesados ​​en eso ahora
  • tmux list-buffers : muestra la información sobre los búferes capturados. El más nuevo tendrá el número 0.
  • tmux show-buffer -b (buffer num) : esto imprime el contenido del buffer dado en un terminal
  • tmux paste-buffer -b (buffer num) : esto pega el contenido del buffer dado como entrada

Sí, esto nos da muchas posibilidades ahora 🙂 En cuanto a mí, configuré un alias simple: alias L="tmux capture-pane; tmux showb -b 0 | tail -n 3 | head -n 1" y ahora cada vez que necesito acceder a la última línea simplemente uso $(L) para obtenerla.

Esto es independiente del flujo de salida que utiliza el progtwig (ya sea stdin o stderr), el método de impresión (ncurses, etc.) y el código de salida del progtwig: los datos solo necesitan mostrarse.

Puede configurar el siguiente alias en su perfil de bash:

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

Luego, al escribir ‘s’ después de un comando arbitrario puede guardar el resultado en una variable de shell ‘it’.

Así que el uso del ejemplo sería:

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

Acabo de destilar esta función bash de las sugerencias aquí:

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

Entonces, simplemente lo haces:

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

Actualización : un usuario anónimo sugirió reemplazar echo por printf '%s\n' que tiene la ventaja de que no procesa opciones como -e en el texto capturado. Entonces, si espera o experimenta tales peculiaridades, considere esta sugerencia. Otra opción es usar cat <<<$grab lugar.

Capture la salida con palos de retroceso:

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

Usualmente hago lo que los otros han sugerido … sin la asignación:

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

Puede obtener más elegante si lo desea:

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

Al decir “me gustaría poder utilizar el resultado del último comando ejecutado en un comando posterior”, supongo que se refiere al resultado de cualquier comando, no solo a encontrar.

Si ese es el caso, xargs es lo que estás buscando.

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

O si le interesa ver el resultado primero:

find . -name foo.txt -print0

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

Este comando trata con múltiples archivos y funciona como un amuleto, incluso si la ruta y / o el nombre de archivo contienen espacio (s).

Observe la parte mv {} / some / new / location / {} del comando. Este comando es comstackdo y ejecutado para cada línea impresa por un comando anterior. Aquí, la línea impresa por el comando anterior se reemplaza en lugar de {} .

Extracto de la página man de xargs:

xargs: crea y ejecuta líneas de comando desde entrada estándar

Para más detalles, consulte la página de man xargs : man xargs

Si todo lo que quiere es volver a ejecutar su último comando y obtener el resultado, una simple variable bash funcionaría:

 LAST=`!!` 

Entonces puede ejecutar su comando en la salida con:

 yourCommand $LAST 

Esto generará un nuevo proceso y volverá a ejecutar su comando, luego le dará la salida. Parece que lo que realmente te gustaría sería un archivo de historia de bash para la salida del comando. Esto significa que tendrá que capturar la salida que bash envía a su terminal. Podrías escribir algo para ver el / dev o / proc necesario, pero eso es desordenado. También podría simplemente crear una “tubería especial” entre su término y bash con un comando en el medio que redirecciona a su archivo de salida.

Pero ambos son una especie de soluciones hacky. Creo que lo mejor sería Terminator, que es un terminal más moderno con registro de salida. Simplemente revise su archivo de registro para ver los resultados del último comando. Una variable bash similar a la anterior simplificaría aún más esto.

Esta es una forma de hacerlo después de ejecutar el comando y decidir que desea almacenar el resultado en una variable:

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

O si sabe de antemano que querrá el resultado en una variable, puede usar los trazos:

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

Como alternativa a las respuestas existentes: Úselo si sus nombres de archivo pueden contener espacios en blanco como este:

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

Como escribí, la diferencia solo es relevante si debe esperar espacios en blanco en los nombres de los archivos.

NB: las únicas cosas incorporadas no son sobre el resultado sino sobre el estado del último comando.

puedes usar !!: 1. Ejemplo:

 ~]$ 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 

Tenía una necesidad similar, en la que quería utilizar la salida del último comando en el siguiente. Muy parecido a un | (tubo). p.ej

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

a algo así como –

 $ which gradle |: ls -altr {} 

Solución: creó esta tubería personalizada. Muy simple, usando xargs –

 $ alias :='xargs -I{}' 

Básicamente nada al crear una mano corta para xargs, funciona como el encanto, y es realmente útil. Acabo de agregar el alias en el archivo .bash_profile.

Esto no es estrictamente una solución de bash, pero puede usar la tubería con sed para obtener la última fila de comandos anteriores.

Primero veamos lo que tengo en la carpeta “a”

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

Entonces, su ejemplo con ls y cd se convertiría en sed y tubería en algo como esto:

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

Entonces, la magia real ocurre con sed, tú canalizas cualquier salida de cualquier comando en sed y sed imprime la última fila que puedes usar como parámetro con marcas de retroceso. O puedes combinar eso con xargs también. (“man xargs” en shell es tu amigo)

El shell no tiene símbolos especiales tipo perl que almacenan el resultado de eco del último comando.

Aprende a usar el símbolo de pipa con awk.

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

En el ejemplo anterior, podrías hacer:

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

es otra forma, aunque sucia.