How can I execute the last line of output in bash?

The command $(!! |& tail -1) should do:

$ 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

As you can see the sudo apt-get install git command is running.

EDIT : Breaking down $(!! |& tail -1)

  • $() is the bash command substitution pattern

  • bash will expand !! to the last executed command

  • |& part is tricky. Normally pipe | will take the STDOUT of the left sided command and pass it as STDIN to the command on the right side of |, in your case the previous command prints its output on STDERR as an error message. So, doing | would not help, we need to pass both the STDOUT and STDERR (or only STDERR) to the right sided command. |& will pass the STDOUT and STDERR both as the STDIN to the right sided command. Alternately, better you can only pass the STDERR:

    $(!! 2>&1 >/dev/null | tail -1)
    
  • tail -1 as usual will print the last line from its input. Here rather than printing the last line you can be more precise like printing the line that contains the apt-get install command:

    $(!! 2>&1 >/dev/null | grep 'apt-get install')
    

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

Bash's history interaction facilities don't offer any mechanism to examine the output of commands. The shell doesn't store that, and history expansion is specifically for commands you have yourself run, or parts of those commands.

This leaves the approach of rerunning the last command and piping both stdout and stderr (|&) into a command substitution. heemayl's answer achieves this, but cannot be used in an an alias because the shell performs history expansion before expanding aliases, and not after.

I can't get history expansion to work in a shell function either, even by enabling it in the function with set -H. I suspect !! in a function will never be expanded, and I'm not sure what it would be expanded to if it were, but right now I'm not sure precisely why it isn't.

Therefore, if you want to set things up so you can do this with very little typing, you should use the fc shell builtin instead of history expansion to extract the last command from the history. This has the additional advantage that it works even when history expansion is disabled.

As shown in Gordon Davisson's answer to Creating an alias containing bash history expansion (on Super User), $(fc -ln -1) simulates !!. Plugging this in for !! in heemayl's command $(!! |& tail -1) yields:

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

This works like $(!! |& tail -1) but can go in an alias definition:

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

After you run that definition, or put it in .bash_aliases or .bashrc and start a new shell, you can simply type @@ (or whatever you named the alias) to attempt to execute the last line of output from the last command.

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
....

If you are using a terminal emulator under X, like gnome-terminal, konsole or xterm, you can use the X selection for something like copy and paste:

Use the primary selection

Select the whole line to run with a triple left click.

Then paste it to the command line using a middle button click.

This should work in most terminal emulators. It makes use of the primary selection, without touching the clipboard - they are separate.
It's selecting, and then combined copy and paste in one operation, technically.

Tags:

Bash