访问Zsh中的最后一个命令(不是上一个命令行)

Hib*_*u57 6 zsh

有没有办法在Zsh中检索最后一个命令的文本?我的意思是最后执行的命令,而不是最后一个命令行。

尝试了什么

为了做到这一点,我发现Zsh和Bash处理历史记录之间存在细微的区别。以下示例暴露了这种差异,这是我在Bash中所做的基础,而在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)
Run Code Online (Sandbox Code Playgroud)

相同的测试,其结果在最后一行有所不同。Bash 在执行之前将命令行添加到历史记录中(我不知道它是否按规范执行),而Zsh似乎在执行之后将命令行添加到历史记录中(相同,我不知道它是否按规范执行) ),因此history | tail -n1不相同。

我想要的是echo "This is command #2"即使在与其他命令位于同一命令行的情况下(当多个命令用分隔时;),也能够检索到前一条命令的文本。

使用bash时,我可以使用history | tail -n1 | sed ...,但由于历史记录处理方面的差异,因此Zsh不能再使用了。

需要实现的目标

关于如何从Zsh中的多命令行命令行获取最后一条命令的任何想法吗?

当命令需要知道上一条命令是什么时,无论该命令在同一行还是在上一行,我都需要它。另一种说法是:我需要访问单个面向命令的历史记录的最后一项,而不是面向行的命令的历史记录的最后一项。

Hib*_*u57 5

试图回答我自己的问题。我仍然欢迎其他人的答案,因为这个并不完美。

至少,有一种方法可以使用$history数组和$HISTCMD索引获得与 Bash 相同的方法。例子:

$ zsh
$ echo "This is command #3"; echo $history[$HISTCMD]
> This is command #3
> echo "This is command #3"; echo $history[$HISTCMD]
Run Code Online (Sandbox Code Playgroud)

这与history | tail -n1Bash 中的相同,我可以sed对此进行应用。但依赖sed正是这个答案不完全完美的原因;使用正则表达式解析命令行很容易出错?尤其是使用与 Zsh 一样复杂的 shell 语法。


小智 5

据我所知,在 zsh 中没有简单的方法。我认为最接近的答案,在交互的shell你的问题是使用历史扩展机制,特别是!#,但你可能感兴趣的!!!!:1和其他的方面。从 zsh 手册:

!# 参考目前输入的当前命令行。

因此,您可以执行以下操作来获取上一个命令:

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

结果是

This is command #1
The previous command was: 'echo This is command #1'
Run Code Online (Sandbox Code Playgroud)

你也可以玩

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

如果您需要!#用双引号括起来,但在这种情况下,您必须;从“最后一个命令”的末尾删除。:s/;//正在这样做。更复杂的命令现在运行良好:

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

给出输出:

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'
Run Code Online (Sandbox Code Playgroud)

但是请注意,在这种情况下#,第一个命令中不应与引号一起出现,因为它将被解释为注释。

注 1

在这里重要的是!#表示到目前为止当前行中存在的所有内容,当前原子除外,因此var在表达式中

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

实际上等于:

var=$(echo echo "This is command #1";)
Run Code Online (Sandbox Code Playgroud)

以便像前面提到的那样echo $var打印echo This is command #1。但是如果我们写简化版,没有变量:

echo "This is command #1"; echo "The previous command was:" !#
Run Code Online (Sandbox Code Playgroud)

then!#将包含附加的、第二个echo及其参数,因此实际上命令将变为:

echo "这是命令 #1"; echo "上一个命令是:" echo "这是命令 #1"; echo "上一个命令是:"

笔记2

这个解决方案显然并不完美,因为您可能已经注意到,引号没有包含在变量中。您可以尝试通过在周围附加单引号!# 而不是直接附加单引号来解决它(否则历史扩展将不起作用)。

注 3

如果您在一行中有超过 3 个或更多的命令,用 , 分隔;,那么您需要更多地使用 zsh 修饰符或外部命令(如 sed 或 awk)。

  • “原子”只是一个词,我认为它最能描述我的想法。关键是 `!#` 并没有完全收集输入到 `!#` 之前的所有内容 - 你可以使用 `var="!#"` 并且 `var="` 在历史扩展后不会出现。我不知道如何命名更好,但是比较两个命令的结果:`echo foo !#` 和 `echo "foo !#"`。第一个包含 3 个原子(`echo`、`foo` 和 `!#`)但是第二个只有两个(`echo` 和 `"foo !#"`)。我在答案中编辑了 **Note 1**,希望现在更清楚了。 (2认同)
  • 从 zsh 手册我猜它是按规范的:`该行被视为完整的直到并包括带有 '!#'` 的单词之前的单词。他们显然把单词称为我所说的原子。但是请记住,例如 bash 的工作方式不同,因此 `echo "foo !#"` 扩展为 `echo "foo echo "foo "` 并且不会产生任何结果,因为引号不匹配。 (2认同)