0

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!

wjandrea
  • 28,235
  • 9
  • 60
  • 81
ijustlovemath
  • 703
  • 10
  • 21
  • You could read directly from the history file. See [this post](https://unix.stackexchange.com/questions/111718/command-history-in-zsh) discussing the niceties of identifying the right file to read. – Noah Dec 08 '21 at 23:13

2 Answers2

2

(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}
}
Gairfowl
  • 2,226
  • 6
  • 9
  • Hi, perhaps my question was unclear; I want to edit the file(s) that correspond to the stdout of the most recently run command, not the files that correspond to the command itself, if that makes sense – ijustlovemath Dec 09 '21 at 05:03
  • Ahh - I missed that completely. I'll add a note, but leave this in place. – Gairfowl Dec 09 '21 at 05:29
  • Note that the `${${(A)=cmd_str}[1]}` trick only works for the first parameter (in most cases) because it doesn’t deal with quotation or spaces; in other words, if `$cmd_str` is something like `vim file\ with\ spaces`, then `${${(A)=cmd_str}[2]}` gives ``file\``, which is typically not so useful. The `(z)` flag can help this case. – Franklin Yu May 25 '22 at 01:38
1

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.
ijustlovemath
  • 703
  • 10
  • 21
  • Try `vim "$(${=history[@][1]})"`, see if it works for your commands (embedded spaces could be a challenge). – Gairfowl Dec 09 '21 at 05:35