¿Cómo puedo ejecutar la última línea de salida en bash?

12

Sé que puedo ejecutar el último comando en bash, con !!, pero ¿cómo puedo ejecutar la última línea de salida?

Estoy pensando en el caso de uso de esta salida:

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

Pero no sé cómo puedo ejecutar eso. Estoy pensando en algo así !!, ¿quizás @@o similar?


Super Usuario también tiene esta pregunta.

Tim
fuente
1
¿Por qué no lo copias y lo pegas?
Piloto6
1
@Tim Desea formas de ejecutar la última línea de salida de un programa, pero para este caso de uso en particular, también existe el enfoque de cambiar la command_not_found_handlefunción de shell o un script que ejecuta (generalmente solo /usr/lib/command-not-found). Creo que podría hacerlo para que se le solicite interactivamente con la opción de instalar automáticamente el paquete, o (probablemente mejor) para que el nombre del paquete se almacene en algún lugar y pueda instalarse ejecutando un comando breve. Eso es lo suficientemente diferente de lo que ha preguntado aquí, puede considerar publicar una pregunta por separado al respecto.
Eliah Kagan
2
También podría ser relevante: github.com/nvbn/thefuck (Ignora el nombre) esta herramienta corrige automáticamente los comandos git / apt / etc :)
bhathiya-perera

Respuestas:

16

El comando $(!! |& tail -1)debe hacer:

$ 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 puede ver, el sudo apt-get install gitcomando se está ejecutando.

EDITAR: Desglosando$(!! |& tail -1)

  • $()es el bashpatrón de sustitución de comando

  • bashse expandirá !!al último comando ejecutado

  • |&La parte es complicada. Normalmente, la tubería |tomará el STDOUT del comando del lado izquierdo y lo pasará como STDIN al comando del lado derecho de |, en su caso, el comando anterior imprime su salida en STDERR como un mensaje de error. Por lo tanto, hacerlo |no ayudaría, necesitamos pasar tanto STDOUT como STDERR (o solo STDERR) al comando del lado derecho. |&pasará STDOUT y STDERR tanto como STDIN al comando del lado derecho. Alternativamente, mejor solo puedes pasar el STDERR:

    $(!! 2>&1 >/dev/null | tail -1)
  • tail -1como de costumbre, imprimirá la última línea desde su entrada. Aquí, en lugar de imprimir la última línea, puede ser más preciso como imprimir la línea que contiene el apt-get installcomando:

    $(!! 2>&1 >/dev/null | grep 'apt-get install')
heemayl
fuente
1
Su enfoque supone que la salida será idéntica la segunda vez. Algunos comandos pueden producir una salida diferente la próxima vez, en cuyo caso es muy difícil predecir qué hará realmente la ejecución de la última línea de su salida.
kasperd
1
@kasperd Tienes razón ... en lo que respecta a este ejemplo específico, la salida debería permanecer igual ...
heemayl
7

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

Las instalaciones de interacción histórica de Bash no ofrecen ningún mecanismo para examinar la salida de los comandos. El shell no almacena eso , y la expansión del historial es específicamente para comandos que usted mismo ha ejecutado o partes de esos comandos.

Esto deja el enfoque de volver a ejecutar el último comando y canalizar tanto stdout como stderr ( |&) en una sustitución de comando. La respuesta de heemayl logra esto, pero no se puede usar en un alias porque el shell realiza la expansión del historial antes de expandir los alias, y no después.

Tampoco puedo hacer que la expansión del historial funcione en una función de shell, incluso habilitándola en la función con set -H. Sospecho que !!una función nunca se expandirá, y no estoy seguro de a qué se expandiría si lo fuera, pero en este momento no estoy seguro de por qué no lo es.

Por lo tanto, si desea configurar las cosas para poder hacerlo con muy poca escritura, debe usar el fcshell incorporado en lugar de la expansión del historial para extraer el último comando del historial. Esto tiene la ventaja adicional de que funciona incluso cuando la expansión del historial está desactivada.

Como se muestra en la respuesta de Gordon Davisson a la creación de un alias que contiene la expansión del historial de bash (en Super User ), simula . El tapar esto en para en comando de heemayl rendimientos:$(fc -ln -1)!!!! $(!! |& tail -1)

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

Esto funciona como $(!! |& tail -1)pero puede ir en una definición de alias:

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

Después de ejecutar esa definición, o ponerla .bash_aliaseso .bashrciniciar un nuevo shell, simplemente puede escribir @@(o lo que sea que haya llamado alias) para intentar ejecutar la última línea de salida del ú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
fuente
¿Hay alguna opción para mover esto a un script separado? Intenté eso y finalmente fallé, porque fc no genera nada ... Como fc sigue el historial de la sesión de shell actual, entonces moverlo a un script separado abre una sesión diferente allí con historial vacío, por supuesto ... agregué algunos comandos sobre la línea fc en el script, y uno de ellos fue ejecutado. Entonces, me pregunto si hay alguna opción para decirle al script, que su "contexto" es la sesión bash, que lo ejecutó. Como, algunos argumentos para #! / Bin / bash o algo así ...
Exterminator13
@ Exterminator13 Si se refiere a una secuencia de comandos independiente que se ejecuta --como ./script, no es que la fuente con .o sourceincorporado - Estoy seguro. Bash se agrega a un archivo al salir del shell o cuando se ejecuta historycon -ao -w(ver help history). Si lo hiciera, los comandos en múltiples shells interactivos se intercalarían (apareciendo en el orden en que los ejecutó). Puede hacer que se agregue de inmediato. . Pero creo que hay más por hacer para fctrabajar en un guión. Sugiero publicar una nueva pregunta sobre esto. Si lo hace, no dude en comentar aquí con un enlace.
Eliah Kagan
2

Si está utilizando un emulador de terminal bajo X, como gnome-terminal, konsoleo xterm, puede usar la selección X para algo como copiar y pegar:

Usa la selección primaria

Seleccione toda la línea para ejecutar con un triple clic izquierdo .

Luego péguelo en la línea de comando con un clic en el botón central .

Esto debería funcionar en la mayoría de los emuladores de terminal. Utiliza la selección principal, sin tocar el portapapeles, están separados.
Está seleccionando, y luego combina copiar y pegar en una operación, técnicamente.

Volker Siegel
fuente
1
@Tim Eso es cierto, lo dejaré claro.
Volker Siegel
1

Como mencionó Eliah Kagan , el shell no almacena la salida del comando. Sin embargo, hay una manera de obtener la última línea de salida del último comando, sin ejecutar el comando nuevamente , si ejecuta la pantalla .

Pon las siguientes líneas en ti ~/.screenrc:

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

Luego puede presionar Ctrl+ atpara insertar la línea anterior en el indicador actual. Sería posible hacer que esto también presione automáticamente enter, pero probablemente sea una mejor idea verificarlo antes de ejecutar y simplemente presionar enter manualmente.

Cómo funciona:

  • register tregistra una cadena en un búfer llamado t. La cadena que sigue es una secuencia de teclas.
  • bind tse une a la clave t(es decir, ejecutar usando Ctrl+ at), process tsignifica evaluar el contenido del buffer nombrado t.
  • La secuencia en sí misma significa:
    • ^a[(= Ctrla[): ingrese al modo de copia
    • kir una línea hacia arriba, 0ir al principio de la línea, y$copiar hasta el final de la línea, (espacio) completar el comando
    • ^a](= Ctrla]): pegar contenido copiado
    • ^a^L(= CtrlaCtrlL) actualice la pantalla (de lo contrario, el contenido pegado no aparecerá, porque no lo escribió usted mismo
atextor
fuente