Bash PS1:与外部命令中的非打印字符换行问题

11

我正在使用一个外部命令来填充我的bash提示符,它每次评估PS1时都会运行。然而,当这个命令输出非可打印字符(比如颜色转义代码)时,我遇到了问题。

以下是一个例子:

$ cat green_cheese.sh 
#!/bin/bash
echo -e "\033[32mcheese\033[0m"

$ export PS1="\$(./green_cheese.sh) \$"
cheese $ # <- cheese is green!
cheese $ <now type really long command>

在处理PS1提示符中的非打印字符时,标准方法是将它们包含在\[\]转义序列中。问题在于,如果您从外部命令执行此操作,则这些转义序列不会被PS1解释器解析:

$ cat green_cheese.sh 
#!/bin/bash
echo -e "\[\033[32m\]cheese\[\033[0m\]"
$ export PS1="\$(./green_cheese.sh) \$"
\[\]cheese\[\] $ # <- FAIL!

有没有特定的转义序列可以从外部命令中使用以实现所需的结果?或者,我是否可以手动告诉提示符要设置多少个字符作为提示宽度?

假设我可以从外部命令中打印任何我想要的内容,并且该命令可以相当智能(例如,计算输出中的字符数)。我还可以使export PS1=...命令变得非常复杂。 但是,颜色的转义代码必须来自外部命令。

提前致谢!

3个回答

24

我无法准确告诉你为什么这样做有效,但请用 bash 在提示符中生成的实际字符替换 \[\]

echo -e "\001\033[32m\002cheese\001\033[0m\002"

[我是从某个 Stack Overflow 帖子中学到的,但现在无法找到了。]

如果要猜测的话,可能是 bash 在执行嵌入到提示符中的命令之前,用两个 ASCII 字符替换\[\],因此当green_cheese.sh 完成时,bash 处理包装器的时间已经过去,所以它们被视为文字处理。避免这种情况的一种方法是使用PROMPT_COMMAND动态构建您的提示符,而不是将可执行代码嵌入到PS1的值中。

prompt_cmd () {
    PS1="$(green_cheese.sh)"
    PS1+=' \$ '
}

PROMPT_COMMAND=prompt_cmd

这样,当PS1定义时,\[\]会被添加到其中,而不是在评估时添加,因此您不需要直接使用\001\002


谢谢。这个完美地运作!实际上,现在我知道要使用的转义代码,我找到了你提到的其他帖子:https://dev59.com/p37aa4cB1Zd3GeqPmBwV#22706988 http://stackoverflow.com/a/14637194/341459 https://dev59.com/_GIk5IYBdhLWcg3wp_iM#19501528 - Lee Netherton
7
你完全正确。\001\002,又称为RL_PROMPT_START_IGNORE和RL_PROMPT_END_IGNORE,是一个文档不足的readline特性。Bash将\[..\]转换为Readline中的\001..\002,但忽略了已经存在的转义字符,因此这个实现细节泄漏出来了(而且它非常有用,你几乎不能把它称为一个bug)。 - that other guy
当我在最近的一篇帖子中看到PROMPT_COMMAND时,我想起了这个问题并回来看了一下,但似乎这个想法已经被提出了。我还有一个想法,即bash实际上可能会从\[\]中产生字符,并尝试进行检查,但是ttyrec的输出令人困惑,所以我放弃了。顺便加一分。 - konsolebox

0

如果您无法编辑生成包含ANSI颜色/控制代码的字符串的代码,则可以事后对其进行包装。

以下内容将在ASCII SOH (^A) 和 STX (^B) 中包含ANSI控制序列,它们分别等同于\[\]

function readline_ANSI_escape() {
  if [[ $# -ge 1 ]]; then
    echo "$*"
  else
    cat  # Read string from STDIN
  fi | \
  perl -pe 's/(?:(?<!\x1)|(?<!\\\[))(\x1b\[[0-9;]*[mG])(?!\x2|\\\])/\x1\1\x2/g'
}

使用方法如下:

$ echo $'\e[0;1;31mRED' | readline_ANSI_escape

或者:

$ readline_ANSI_escape "$string"

作为额外的好处,多次运行该函数不会重新转义已经转义过的控制码。

-1

我怀疑如果你在第一个例子后面回显$PS1的值,你会发现它的值是绿色的单词“cheese”。(至少当我运行你的例子时是这样的。)乍一看,这正是你想要的——绿色的单词“cheese”!但是你真正想要的是单词cheese前面带有产生绿色的转义码。通过使用echo的-e标志,你所做的是生成一个已经评估过转义码的值。

这对于颜色规范来说是有效的,但正如你所发现的那样,它将“非打印序列”标记搞成了$PS1解释器无法正确理解的东西。

幸运的是,解决方案很简单:去掉-e标志。echo将保留转义序列不变,$PS1解释器将做正确的事情™。


听起来很公平,但是不起作用。chepner已经描述了在调用外部命令之前如何发生转义。 - phil294

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接