How do I access the last command from a zsh function?

451 views Asked by At

I'd like to do something like this:

function invim () {
  vim $(!!)
}

However when I run it, it says: invim:1: command not found: !!

I assume this is because !! is an interactive shell expansion not available in noninteractive scripts/functions. In bash I could use the history command to accomplish this, but when I tried to use it, it seems to hijack my readline and not allow up-arrow completion to work anymore.

Any help is appreciated!

2

There are 2 answers

1
ijustlovemath On BEST ANSWER

Using Gairfowl's answer with the associative array, I was able to formulate an appropriate solution:

function invim {                                              
    vim $(bash -c ${history[@][1]})            
}

Here's how it works:

  1. ${history[@][1]} is the text of the latest command, for example, find . -type f -name "*.txt"
  2. Run that command in a shell using bash -c
  3. The resulting stdout is captured by the enclosing $(), and is then passed to vim as the filename(s) to edit. There are no enclosing quotes here to allow for multiple line outputs to all be opened.
3
Gairfowl On

(nb: this is not a complete answer; see comments and https://stackoverflow.com/a/70285039/9307265)


Recent commands are available in the history associative array, so this should be similar to the function in your question:

function invim {
    vim ${history[@][1]}
}

The history[@] expansion gets the commands from the associative array, while [1] references the first item, which is guaranteed to be the most recent.

But that may not do exactly what you're looking for. Unlike bash, zsh doesn't re-parse variable expansions as words, so executing the function right after calling foo --opt will result in an attempt to edit a file named foo --opt, which probably doesn't exist. We can get just the first word by using word expansion with ${=...}, converting it an array with the (A) parameter expansion flag, and adding another [1] index operator.

Also, in many cases this function would only find a file in that's in the current directory. With the :c modifier, the function can search the PATH and find the source file.

Putting it all together:

function invim {
    vim ${${(A)=history[@][1]}[1]:c}
}