如何打印当前的bash提示符?

23

这个问题很简单。我想在我的Bash脚本中评估PS1的当前值。

谷歌上的所有材料都是关于升级它的教程,但是我想要评估看看它在我的当前终端或者至少是某个终端中是如何展示的。

有没有任何软件/函数可以帮助我实现这一点?当然,我希望将所有转义字符都计算在内,因此echo $PS1在我的情况下并不太有用。


1
你看过 https://dev59.com/BmLVa4cB1Zd3GeqPwXN5 吗? - Jayesh Bhoi
不,我想要的是user:~/dir/$,而不是[\e[01m][\e[32m]\u:[\e[01m][\e[34m]\w/$[\e[00m]。 - Piotr Zierhoffer
1
可能是[回显扩展PS1]的重复问题(https://dev59.com/hXA75IYBdhLWcg3wJFcL)。 - Eliah Kagan
6个回答

22
使用参数转换技术,可以在 Bash 4.4+ 版本中通过以下命令打印提示字符串:echo "${PS1@P}"
[adamhotep@tabasco ~]$ echo "the prompt is '${PS1@P}'"
the prompt is '[adamhotep@tabasco ~]$'
[adamhotep@tabasco ~]$ TEST_STRING='\u is dining at \t using \s \V'
[adamhotep@tabasco ~]$ echo "${TEST_STRING}"
\u is dining at \t using \s \V
[adamhotep@tabasco ~]$ echo "${TEST_STRING@P}"
adamhotep is dining at 21:45:10 using bash 5.0.3
[adamhotep@tabasco ~]$ 

Bash参考手册Shell参数扩展页面:

${parameter@operator}

Parameter transformation. The expansion is either a transformation of the value of parameter or information about parameter itself, depending on the value of operator.
Each operator is a single letter:

Q  The expansion is a string that is the value of parameter quoted in a
   format that can be reused as input.
E  The expansion is a string that is the value of parameter with backslash
   escape sequences expanded as with the $'…' quoting mechanism.
P  The expansion is a string that is the result of expanding the value of
   parameter as if it were a prompt string (see PROMPTING below).
A  The expansion is a string in the form of an assignment statement or
   declare command that, if evaluated, will recreate parameter with its
   attributes and value.
a  The expansion is a string consisting of flag values representing
   parameter's attributes.

If parameter is @ or *, the operation is applied to each positional parameter in turn, and the expansion is the resultant list. If parameter is an array variable subscripted with @ or *, the operation is applied to each member of the array in turn, and the expansion is the resultant list.

(另请参阅此答案来自重复问题Echo expanded PS1。)

 

Z Shellzsh)可以使用${(%%)PS1}或其内置的print命令的-P标志来实现此功能:

[adamhotep@tabasco ~]% echo "the prompt is '${(%%)PS1}'"
the prompt is '[adamhotep@tabasco ~]%'
[adamhotep@tabasco ~]% print -P "the prompt is '$PS1'"
the prompt is '[adamhotep@tabasco ~]%'
[adamhotep@tabasco ~]% TEST_STRING="%n is dining at %* using %N $ZSH_VERSION"
[adamhotep@tabasco ~]% echo "$TEST_STRING"
%n is dining at %* using %N 5.7.1
[adamhotep@tabasco ~]% echo "${(%%)TEST_STRING}"
adamhotep is dining at 11:49:01 using zsh 5.7.1
[adamhotep@tabasco ~]% print -P "$TEST_STRING"
adamhotep is dining at 11:49:07 using zsh 5.7.1
[adamhotep@tabasco ~]% 

Zsh扩展和替换手册告诉我们:

参数扩展标志。如果大括号后面直接跟着开括号,那么一直到匹配的闭括号的字符串将被视为标志列表。在重复标志有意义的情况下,重复不必连续;例如,(q%q%q) 的含义与更易读的 (%%qqq) 相同。支持以下标志:

%    以与提示符(请参见提示符扩展)相同的方式扩展结果单词中的所有% 转义。如果给出了两次此标志,则根据PROMPT_PERCENTPROMPT_SUBSTPROMPT_BANG 选项的设置,在结果单词上执行完整的提示符扩展。

Zsh内置命令文档中的print

-P 执行提示扩展(参见Prompt Expansion)。与 -f 结合使用,提示转义序列仅在插入的参数中解析,而不在格式字符串中解析。


1
这绝对是这里最好的答案,简单而且正确地工作,谢谢。 - Piotr Zierhoffer
注意 PS1 不是环境变量,您无法导出它,使用 bash 手动传递它将被过滤掉...不要尝试。只需将其重命名为其他名称,例如 PSX。 - Ray Foss
我在bash 5.1.8中遇到了一个坏的替换错误,有没有人有解决办法? - Daniel Porteous
这是在Mac上,但它使用了来自homebrew的升级版bash。也许我的PS1里有一些不好的东西,这个扩展无法处理。 - Daniel Porteous
1
@DanielPorteous - 我认为最好将其作为一个新的SO问题来解决。如果您将其发布为一个问题(附带您的$PS示例和用例!),请在此处添加一个链接评论,然后我们可以删除今天的其他评论,以减少这个空间中的杂音。 - Adam Katz
显示剩余2条评论

6
我会这样获取它:
echo $PS1

然后使用编辑器进行编辑。之后为了测试(这是在会话期间设置的):

PS1='\[\033[1m\]\[\033[34m\]\u\[\033[90m\]@\[\033[01;35m\]\h:\[\033[01;32m\]\W\[\033[0m\]$ '

(\u 代表用户,\h 代表主机,\w 代表完整路径,\W 代表短路径)

如果我喜欢这个设置,我会通过更改 ~/.bashrc 中的 PS1 的值来使其成为永久设置。

附言:

要查看所有全局变量:

printenv

或者:

printenv <name_of_var_to_see>

但这正是我不想要实现的 - 它打印出变量的原始值,而我需要它被渲染 - 即使用所有颜色、\x 占位符等进行评估。@anishsane 的答案虽然复杂,但运行良好。在评论中链接的“可能重复”的问题中发布了更好的方法。 - Piotr Zierhoffer
执行第二步。我会告诉你如何查看当前版本,以便在需要时还原。第二步是PS1 = .... - NFSpeedy

4

还有一种可能性,使用script实用程序(Ubuntu上的bsdutils软件包的一部分):

$ TEST_PS1="\e[31;1m\u@\h:\n\e[0;1m\$ \e[0m"
$ RANDOM_STRING=some_random_string_here_that_is_not_part_of_PS1
$ script /dev/null <<-EOF | awk 'NR==2' RS=$RANDOM_STRING
PS1="$TEST_PS1"; HISTFILE=/dev/null
echo -n $RANDOM_STRING
echo -n $RANDOM_STRING
exit
EOF
<prints the formatted prompt properly here>

script命令可以生成指定的文件,并在stdout上显示输出。如果省略文件名,则会生成一个名为typescript的文件。

由于在这种情况下我们不关心日志文件,因此将文件名指定为/dev/null。相反,将script命令的stdout传递给awk进行进一步处理。

  1. 整个代码也可以封装到函数中。
  2. 此外,输出提示也可以分配给变量。
  3. 该方法还支持解析PROMPT_COMMAND...

编辑:
看起来新版本的script会在typescript中回显管道的stdin。为了处理这个问题,可以更改上述机制为:

$ TEST_PS1="\e[31;1m\u@\h:\n\e[0;1m\$ \e[0m"
$ RANDOM_STRING=some_random_string_here_that_is_not_part_of_PS1
$ script /dev/null <<-EOF | awk '{old=current; current=$0;} END{print old}' RS=$RANDOM_STRING
PS1="$TEST_PS1"; HISTFILE=/dev/null
alias $RANDOM_STRING=true
$RANDOM_STRING
$RANDOM_STRING
EOF

<prints the formatted prompt properly here>

说明:

尝试手动在终端上输入这些命令。将这些命令复制到 heredoc 下,并使用鼠标中键粘贴。脚本命令的标准输出会包含非常相似的内容。

例如,对于上面的示例,脚本命令的输出如下:

PS1="\e[31;1m\u@\h:\n\e[0;1m$ \e[0m"; HISTFILE=/dev/null
alias some_random_string_here_that_is_not_part_of_PS1=true
some_random_string_here_that_is_not_part_of_PS1
some_random_string_here_that_is_not_part_of_PS1
 \e[0m"; HISTFILE=/dev/nullhsane-dev : ~/Desktop $ PS1="\e[31;1m\u@\h:\n\e[0;1m$ 
anishsane@anishsane-dev:
$ alias some_random_string_here_that_is_not_part_of_PS1=true
anishsane@anishsane-dev:
$ some_random_string_here_that_is_not_part_of_PS1
anishsane@anishsane-dev:
$ some_random_string_here_that_is_not_part_of_PS1
anishsane@anishsane-dev:
$ exit

使用“some_random_string_here_that_is_not_part_of_PS1”作为分隔符(awk的记录分隔符)来拆分stdout,并打印倒数第二条记录。

编辑2:

另一种机制(使用bash源代码和gdb):

$ gdb -batch -p $$ -ex 'call bind_variable("expanded_PS1", decode_prompt_string (get_string_value ("PS1")), 0)'
$ echo "$expanded_PS1"
<prints the formatted prompt properly here>
  1. 这里有一个小问题。在PS1中的\[\]字符串将被打印为\1/\2。您可以使用tr -d '\1\2' <<< "$expanded_PS1"来删除它们。
  2. 如果出现gdb无法附加到进程的错误(在Ubuntu中似乎经常发生),请使用sudo运行gdb

请注意,您需要使用类似于“less”的工具查看“my_filename”,以便正确显示提示符中的转义字符。 - chepner
编辑以在屏幕上仅输出解析的“PS1”。 :) - anishsane
1
你可以使用类似于 colcrt 的工具部分清除转义码。它是为 man 页面而设计的,不适用于 script 输出,因此并不完美,但这是一个开始。 - tripleee
echo -n $RANDOM_STRING 是什么意思?它是必需的吗?为什么要执行两次?awk 对随机字符串做了什么?更详细地解释上述命令的工作原理将会很有帮助。 - Nathan Craike
已添加了解释。 - anishsane

3
另一种方法是使用eval,通过打印提示来处理任何扩展(不确定为什么括号还在)。这种方法可能比@anishsane的方法更简单,但可能不够健壮。
show-prompt() {
    eval 'echo -en "'$PS1'"' | sed -e 's#\\\[##g' -e 's#\\\]##g'
}
# To show it in a function registered with `complete -F` on
# a single tab, and keep the user's input:
show-prompt
echo -n "${COMP_WORDS[@]}"

我曾经在GitHub问题上探索过这个问题。


“registered with complete -F” 的确切含义是什么?我正在尝试从Octave中使用它,以便显示常规提示,然后将用户的输入存储为命令,但提示不会显示;就像这样:octave --eval "system('./showprompt'); usercommand = input('', 's')" - nightcod3r
complete -F 是用于 Bash 的一个命令补全习语;要了解更多信息,请参见 bash -c 'help complete'。我的上面的评论很简洁,但我的意思是你可以将这两行代码注入到传递给 Bash 的 complete -F <function> 函数中。通过这种方式,您可以创建一个具有长时间运行更新(例如 bazel 构建系统的 CLI 初始化)的完成方法,但您至少可以向用户显示它正在工作而不是冻结。不幸的是,我没有关于 Octave 的特定应用程序的建议 :( - Eric Cousineau
这似乎对我的提示符起作用,但转义序列\u\h\W却不行(我碰巧在我的PS1变量中有这些序列;也许还有更多类似的序列)。 - HelloGoodbye

0

请尝试以下命令

echo $PS1 | 
sed -e s/'\\d'/"$(date +'%a %b %_d')"/g | 
sed -e s/'\\t'/"$(date +'%T')"/g | 
sed -e s/'\\@'/"$(date +'%r')"/g | 
sed -e s/'\\T'/"$(date +'%r'| awk {'print $1'})"/g | 
sed -e s/'\\e'//g | sed -e s/'\\h'/"$HOSTNAME"/g | 
sed -e s/'\\h'/"$HOSTNAME"/g | 
sed -e s/'\\H'/"$HOSTNAME"/g | 
sed -e s/'\\u'/"$USER"/g | 
sed -e s@'\\W'@"$(pwd)"@g | 
sed -e s/'\\w'/"$(pwd | sed -e s@$HOME@'~'@g )"/g | 
sed -e s/"\\\\"//g | 
sed -e s/"\\["//g | 
sed -e s/"\\]"/*/g | 
cut -d'*' -f2 | 
cut -d';' -f2 | 
sed s/\ //g | 
sed -e s/[a-z]$/"$([ "$USER" != "root" ] && echo \$ || echo \#)"/g

你能解释一下当你的代码运行时应该发生什么吗?在我们知道它应该做什么之前,有些人并不想只是“试一试”。 :-) - EJ Mak
我不喜欢这个答案,但看起来他们正在搜索PS1转义序列,并用正确的值替换它们。它并没有像其他人那样完全符合要求。 - Matthew Strasiotto

-1

编辑 /etc/bashrc 文件

您可以使用此作为示例并检查输出

    # If id command returns zero, you’ve root access.
if [ $(id -u) -eq 0 ];
then # you are root, set red colour prompt
  PS1="\\[$(tput setaf 1)\\]\\u@\\h:\\w #\\[$(tput sgr0)\\]"
else # normal
  PS1="[\\u@\\h:\\w] $"
fi

1
但这并没有真正解决我的问题。我无法编程获取显示为我的命令提示符的字符串。 - Piotr Zierhoffer

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