Access to last command in Zsh (not previous command line)

zsh

Is there a way to retrieve the text of the last command in Zsh? I mean the last executed command, not the last command line.

What was tried

Trying to do it, I discovered a subtle difference between Zsh and Bash handling of history. This difference is exposed in the following sample, which is the base of what I was doing in Bash, and which does not work in Zsh.

$ zsh
$ echo "This is command #1"
> This is command #1
$ echo "This is command #2"; echo $(history | tail -n1)
> This is command #2
> 3807 echo "This is command #1"

$ bash
$ echo "This is command #1"
> This is command #1
$ echo "This is command #2"; echo $(history | tail -n1)
> This is command #2
> 4970 echo "This is command #2"; echo $(history | tail -n1)

Same tests, whose result differs in the last line. Bash appends the command line to the history, before execution (I don't know if it's by specification), while Zsh seems to append the command line to the history, after execution (the same, I don't know if it's by specification), thus history | tail -n1 does not give the same.

What I want, is to be able to retrieve echo "This is command #2", that is, the text of the previous command even when it's on the same command line as other commands (when there are multiple command separated with ;).

With bash, I could use history | tail -n1 | sed ..., but this does not work any more with Zsh, due the difference in history handling.

What needs to be achieved

Any idea on how to get the last command from a multi‑command command line in Zsh?

I need it for when a command needs to know what was the previous command, whatever that command was on the same line or the previous line. Another way to say it, could be: I need to access the last item of a single command oriented history, not of a command(s) line oriented history.

Best Answer

As far as I'm aware of there is no easy way to this in zsh. I believe the closest answer to your question in interactive shell is to use history expansion mechanism, especially !#, but you may be interested in !!, !!:1 and others as well. From zsh manual:

!# Refer to the current command line typed in so far.

So you can do the following to get previous command:

echo "This is command #1"; var=$(echo !#) && echo "The previous command was:" \'$var\'

And the result is

This is command #1
The previous command was: 'echo This is command #1'

You can also play with

echo "This is command 1"; var=$(echo "!#:s/;//") && echo "The previous command was:" \'$var\'

if you would need to enclose !# with double quotes, but in this case you have to remove ; from the end of the "last command". :s/;// is doing that. More sophisticated commands are now working well:

ls >/dev/null 2>&1; var=$(echo "!#:s/;//") && print "The previous command was:" \'$var\'

gives the output:

ls >/dev/null 2>&1; var=$(echo "ls >/dev/null 2>&1") && print "The previous command was:" $var
The previous command was: 'ls >/dev/null 2>&1'

Notice however that in this case # should not be present in the first command together with quotes as it will be interpreted as comment.

Note 1

It is important here that !# represents everything what is present in the current line so far except a current atom, thus var in the expression

echo "This is command #1"; var=$(echo !#) && echo "The previous command was:" \'$var\'

is in fact equal to:

var=$(echo echo "This is command #1";)

so that echo $var prints echo This is command #1 as mentioned before. But if we write simplified version, without variable:

echo "This is command #1"; echo "The previous command was:" !#

then !# would contain additional, second echo and its argument, so actually command would become:

echo "This is command #1"; echo "The previous command was:" echo "This is command #1"; echo "The previous command was:"

Note 2

This solution is obviously not perfect because, as you may noticed, quotes are not included into variable. You can try to workaround about it with appending single quotes around !# but not directly (otherwise history expansion won't work).

Note 3

If you have more than 3 or more commands in one line, separated with ;, than you would need to play a little bit more with zsh modifiers or with outside commands like sed or awk.

Related Topic