如何检测是否向 shell 提示符提供了命令

如何检测是否向 shell 提示符提供了命令

我想编写一个PROMPT_COMMAND响应之前提供给命令提示符的任何内容的代码。例如,要在广泛、信息丰富的提示或简单、紧凑的提示之间切换,如下所示:

mikemol@serenity ~ $ echo hi
hi
$ echo ho
ho
$ echo hum
hum
$
mikemol@serenity ~ $

仅当值不为空时,值才会添加到 shell 的历史记录中,因此我不能简单地测试最近的历史记录条目是否为空。set | grep some_command运行后运行some_command没有给我任何结果,因此似乎不存在包含该信息的环境变量。

我主要使用bash,但对 POSIX 兼容的解决方案和其他 shell 感到好奇。

答案1

PROMPT_COMMAND我最终根本不需要。感谢克里斯托弗为我指明了正确的方向。

相反,考虑这个文件ps1.prompt

${__cmdnbary[\#]+$(
    echo '\u@\h: \w' # Your fancy prompt goes here, with all the usual special characters available.
) }${__cmdnbary[\#]=}\$

然后我可以将其输入到我的PS1

PS1=$(cat ps1.prompt)

(你不必这样做,但我发现它对于插图和编辑来说很方便。)

所以我们看到:

mikemol@zoe:~$ echo hi
hi
mikemol@zoe:~$ echo ho
ho
mikemol@zoe:~$ echo hum
hum
mikemol@zoe:~$ 
mikemol@zoe:~$ PS1=$(cat ps1.prompt)
$ 
mikemol@zoe: ~ $ echo hi
hi
$ echo ho
ho
$ echo hum
hum
$ 
mikemol@zoe: ~ $ 

我们正在使用数组 hack在这里展示bash,但我们不使用的参数替换,而是${parameter:-word}使用${parameter+word}so 我们仅在没有先前的命令运行时触发。

这需要一些解释,因为我们被迫在逻辑中使用双重否定。

如何${__cmdnbary[\#]-word}${__cmdnbary[\#]=}运作

在最初的数组破解演示中,${__cmdnbary[\#]-word}${__cmdnbary[\#]=}使用了该构造。 (为了清楚起见,我已替换$?word)。如果您对参数扩展和数组不是特别熟悉(我不是),那么根本不清楚发生了什么。

首先,了解一下\#

手册

\#该命令的命令编号

...

命令编号是当前 shell 会话期间执行的命令序列中的位置。

这意味着\#只会改变当且仅当执行命令。如果用户在提示符下输入空行,则不会执行任何命令,因此\#不会更改。

${__cmdnbary[#]=}中空字符串的设置

${__cmdnbary[\#]=}使用参数扩展。回到手册

${parameter:=word}

指定默认值。如果参数未设置或为空,则将单词的扩展分配给参数。然后替换参数的值。

因此,如果__cmdnbary[\#]未设置或为 null,则此构造将分配一个空字符串(word在我们的例子中是一个空字符串),并且整个构造将在我们的输出中替换为相同的空字符串。

__cmdnbary[\#]将要总是当我们第一次看到它时,它会被取消设置或为 null,因为 # 是单调的——它总是递增或保持不变。 (也就是说,直到它循环,可能大约 2^31 或 2^63,但我们还会遇到其他问题长的在我们到达那里之前。我将这个解决方案描述为有点黑客行为是有原因的。)

条件式在${__cmdnbary[\#]-word}

${__cmdnbary[\#]-word}是另一个参数扩展。从手册中:

${parameter:-word}

使用默认值。如果参数未设置或为空,则替换单词的扩展。否则,将替换参数的值。

所以,如果数组条目\#未设置或为空word在其位置使用。由于我们在检查之前不会尝试分配__cmdnbary[\#](使用替换${parameter:=word}),因此第一次检查给定值时\#应该会发现数组中的该位置未设置。

bash使用稀疏数组

对于那些习惯 C 风格数组的人需要澄清一点。bash实际上使用稀疏数组;直到你分配某物到数组中的某个位置,该位置未设置。空字符串与“null 或 unset”不同。

为什么我们使用 ${__cmdnbary[#]+word}${__cmdnbary[#]=} 代替

${__cmdnbary[\#]+word}${__cmdnbary[\#]=}和 ${__cmdnbary[#]-word}${__cmdnbary[#]=} look very siilar. The *only* thing we change between the two constructs can be found in the first portion; we use${parameter:+word} instead of${parameter:-word}`。

请记住${parameter:-word},只有当为 null 或未设置word时,才会出现- 在我们的例子中,只有当我们parameter没有设置数组中的位置,当且仅当\#增加时我们不会这样做,这只有在我们刚刚执行命令时才会发生。

这意味着,对于${parameter:-word},我们只会呈现,word如果我们没有执行了一个命令,这与我们想要做的恰恰相反。所以,我们用${parameter:-word}它来代替。再次,来自手册:

${parameter:+word}

使用替代值。如果参数为空或未设置,则不会替换任何内容,否则将替换单词的扩展。

这是(不幸的是)更需要理解的双重否定逻辑,但你就是这样。

提示本身

我们已经解释了切换机制,但是提示本身又如何呢?

在这里,我用来$( ... )包含提示的实质内容。主要是为了我自己的可读性;你不必那样做。您可以替换$( ... )为通常在变量赋值中填充的任何内容。

为什么它是黑客攻击?

还记得我们如何向稀疏数组添加条目吗?我们不会删除这些条目,因此数组将永远增长,直到退出 shell 会话为止;外壳漏了PS1。据我所知,没有办法未设置提示中的变量或数组位置。你可以尝试一下$(),但你会发现它不起作用;对子 shell 内的变量命名空间所做的更改不会应用于派生子 shell 的空间。

可能尝试在结果文件中使用mktmpEarly in your .bashrc、 before assignment 和 stuff 信息;PS1然后您可以将当前的内容\#与存储在其中的内容进行比较,但现在您的提示取决于磁盘 I/O,这是在紧急情况下将自己锁定的好方法。

相关内容