'How to get last command run without using `!!`?

I'm trying to alias _! to sudo the last command, but I'm running into roadblocks. !! doesn't seem to work in my .zshrc file, and sed has given me repeated problems. I tried using the following command, and several variations of it, but to no avail.

history | tail -1 | sed -e 's/[^0-9\*\ ]+/\0/g'

However, this still interpreted the piped input as a file, instead of a string of text. I also tried a variation using awk:

history | tail -1 | awk '{ gsub("/[^0-9\*\ ]+", "") ; system( "echo" $1 ) }'

I'm sure I'm just having some trouble putting the commands in correctly, but some help would be appreciated.



Solution 1:[1]

You can use the fc built-in to access the history programmatically. For example, I believe this will behave as you wish:

alias _!='fc -e "sed -i -e \"s/^/sudo /\""'

With no arguments, fc (short for "fix command") fires up $EDITOR on your previous command and runs the result of your editing. You can specify a different editor with the -e option; here, I'm specifying a non-interactive one in the form of a sed command that will insert sudo in front of the command line.

The command assumes GNU sed. As written, it will also work with the version that ships on modern BSD/macOS, but by way of a hackcident: it treats the -e as an argument to -i instead of a new option. Since the -e is optional with only one expression, this works fine, but it means that sed makes a backup of the temp file with -e on the end, which will hang around after the command completes. You can make that cleaner by using this alternative version on those systems:

alias _!='fc -e "sed -i \"\" -e \"s/^/sudo /\""'

(That won't work with GNU sed, which sees the empty string argument as a filename to operate on...)

On older systems, a more portable solution could use ed:

alias _!="fc -e 'ed -s <<<$'\''s/^/sudo /\nw\nq'\'"

You can often get away with something simpler, like sudo $(fc -ln -1) (-l = list commands, -n = without numbers, -1 = only the last command), but in general you will run into quoting issues, since the command is output by fc the way it was typed:

% touch '/etc/foo bar'
touch: /etc/foo bar: Permission denied
% sudo $(fc -ln -1)
touch: '/etc/foo: No such file or directory

None of the above is limited to zsh, btw; fc dates to the original version of ksh, so it will also work in bash, etc.

Solution 2:[2]

This fc command will always give most recently executed command in zsh and in bash:

fc -ln -1

As per help fc:

 -l (letter el) list lines instead of editing
 -n omit line numbers when listing

-1 (minus one) gets the just executed command.

Solution 3:[3]

Found a amazing widget to sudo:

sudo-command-line() {
    [[ -z $BUFFER ]] && zle up-history
    [[ $BUFFER != sudo\ * ]] && {
      typeset -a bufs
      bufs=(${(z)BUFFER})
      if (( $+aliases[$bufs[1]] )); then
        bufs[1]=$aliases[$bufs[1]]
      fi
      bufs=(sudo $bufs)
      BUFFER=$bufs
    }
    zle end-of-line
}
zle -N sudo-command-line
bindkey "\e\e" sudo-command-line

Author:lilydjwg


The following is the way to run the last command in command:


fc -ln -1 is the simplest way, but one problem, when run something with some spaces at the beginning of the command, this command won't shown up in history, anything based on history won't work properly.

So we need ZLE(Zsh Line Editor) to store the command manually.

Store_Your_Command () {
    if [[ -z $BUFFER ]]
    then
        # If nothing input, just clear the screen
        zle clear-screen
    else
        zle accept-line
        # Remember the last command, useful in some alias
        # Add space at the beginning of a command, this command wont
        # show up in history, so use variables to store the command
        LAST_COMMAND=$CURRENT_COMMAND
        CURRENT_COMMAND=$BUFFER
    fi
}
# Create a user-defined widget
zle -N Store_Your_Command
# Bind it to the **Enter** key
bindkey "^M" Store_Your_Command

Then whenever we press enter to run a command, this command will be stored in $CURRENT_COMMAND, and the last command will be stored in $LAST_COMMAND.

Want to run the last command? Just run eval $LAST_COMMAND, you can also put it to your alias.


When some alias in the last command, zsh wont run the last command correctly, so we need to expand our alias: when we input an alias, replace the alias to the original command/content, with help of the builtin zle: _expand_alias.

First, delete the widget we just added. Add those to your .zshrc:

# When input space, expand alias -----------------------------------{{{
expand_alias_space () {
    zle _expand_alias
    zle self-insert
}
zle -N expand_alias_space
bindkey " " expand_alias_space
# }}}
# When input enter, expand alias -----------------------------------{{{
expand_alias_enter () {
    if [[ -z $BUFFER ]]
    then
        zle clear-screen
    else
        zle _expand_alias
        zle accept-line
        # Remember the last command, useful in some alias
        # Add space at the beginning of a command, this command won't
        # show up in history, so use variables to store the command
        LAST_COMMAND=$CURRENT_COMMAND
        CURRENT_COMMAND=$BUFFER
    fi
}
zle -N expand_alias_enter
bindkey "^M" expand_alias_enter
# }}}

Now we can expand alias to the original command/content by press Space key or just press Enter key to run the command, and use eval $LAST_COMMAND to run the last command without any problems.


But it will call another problem when run a command use eval $LAST_COMMAND twice:

zsh: job table full or recursion limit exceeded

We need to replace eval $LAST_COMMAND to the real command, because $LAST_COMMAND always change. We write a function run the last command like this

# Echo the last command 
fun()
{
    # The command we need to run in this function
    CURRENT_COMMAND="echo \[`echo $LAST_COMMAND`\]"
    # run the command 
    eval $CURRENT_COMMAND
}

The command stored in $CURRENT_COMMAND wont change like eval $LAST_COMMAND does. problem sloved.


No more problem I hope

Solution 4:[4]

If I want . to be the alias for last command,

lastcmd() {
    # start climbing back in history, checking for alias
    n=-1
    lc=$(fc -ln $n $n)
    # "." is checked because I have aliased lastcmd to "."
    while [ "$lc" = "lastcmd" ] || [ "$lc" = "." ]
    do
        n=$(( $n - 1 ))             
        lc=$(fc -ln $n $n)      
    done
    eval ${lc}
}

alias .=lastcmd

This is pretty simple and works well for me. Replace the second condition in while with whatever alias you end up using.

Solution 5:[5]

Add this to your .zshrc --

func preexec() {
  export LAST_COMMAND="$1"
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 anubhava
Solution 3
Solution 4
Solution 5 Harjot Gill