Bash 如何扩展PS1?

Bash 如何扩展PS1?,bash,ps1,Bash,Ps1,我有一个shell脚本,它在几个目录()中运行相同的命令。对于每个目录,我希望它显示当前提示符+将在那里运行的命令。如何获取与解码的PS1对应的字符串?例如,我的默认PS1是 ${debian\u chroot:+($debian\u chroot)}\[\e[1;32m\]\u\[\e[0m\]@[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$(%s)]$ 我想回显结果提示username@hostname:/path$,最好(但不一定)漂亮的颜

我有一个shell脚本,它在几个目录()中运行相同的命令。对于每个目录,我希望它显示当前提示符+将在那里运行的命令。如何获取与解码的PS1对应的字符串?例如,我的默认PS1是

${debian\u chroot:+($debian\u chroot)}\[\e[1;32m\]\u\[\e[0m\]@[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$(%s)]$

我想回显结果提示
username@hostname:/path$
,最好(但不一定)漂亮的颜色。粗略地看一下Bash手册并没有发现任何明确的答案,而
echo-e$PS1
只评估颜色。

为什么不自己处理
$PS1
转义替换?一系列替换,例如:

p="${PS1//\\u/$USER}"; p="${p//\\h/$HOSTNAME}"
顺便说一下,zsh能够解释提示转义

print -P '%n@%m %d'


开源软件的一大优势是,源代码是开放的:-)

Bash本身不提供此功能,但您可以使用各种技巧来提供子集(例如用
$USER
替换
\u
,等等)。但是,这需要大量重复功能,并确保代码与
Bash
将来所做的任何事情保持同步

如果您想获得提示变量的所有功能(并且您不介意通过一些编码弄脏您的手(如果您介意,为什么会在这里?),那么添加到shell本身就足够简单了

如果您下载
bash
(我正在查看版本4.2)的代码,则会有一个
y.tab.c
文件,其中包含
解码提示字符串()
函数:

char *decode_prompt_string (string) char *string; { ... }
此函数用于计算用于提示的
PSx
变量。为了允许将此功能提供给shell本身的用户(而不是仅由shell使用),您可以按照以下步骤添加内部命令
evalps1

首先,更改
support/mkversion.sh
,这样您就不会将其与“真实的”
bash
混淆,并且FSF可以出于保修目的拒绝所有知识:-)只需更改一行(我添加了
-pax
位):

其次,更改
内置/Makefile.in
以添加新的源文件。这需要许多步骤

(a) 将
$(srcdir)/evalps1.def添加到
DEFSRC
的末尾

(b) 在文件的末尾添加
evalps1.o

(c) 添加所需的依赖项:

evalps1.o: evalps1.def $(topdir)/bashtypes.h $(topdir)/config.h \
           $(topdir)/bashintl.h $(topdir)/shell.h common.h
第三,添加
内置/evalps1.def
文件本身,这是运行
evalps1
命令时执行的代码:

This file is evalps1.def, from which is created evalps1.c.
It implements the builtin "evalps1" in Bash.

Copyright (C) 1987-2009 Free Software Foundation, Inc.

This file is part of GNU Bash, the Bourne Again SHell.

Bash is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Bash is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Bash.  If not, see <http://www.gnu.org/licenses/>.

$PRODUCES evalps1.c

$BUILTIN evalps1
$FUNCTION evalps1_builtin
$SHORT_DOC evalps1
Outputs the fully interpreted PS1 prompt.

Outputs the PS1 prompt, fully evaluated, for whatever nefarious purposes
you require.
$END

#include <config.h>
#include "../bashtypes.h"
#include <stdio.h>
#include "../bashintl.h"
#include "../shell.h"
#include "common.h"

int
evalps1_builtin (list)
     WORD_LIST *list;
{
  char *ps1 = get_string_value ("PS1");
  if (ps1 != 0)
  {
    ps1 = decode_prompt_string (ps1);
    if (ps1 != 0)
    {
      printf ("%s", ps1);
    }
  }
  return 0;
}
出现的
bash
可执行文件可以重命名为
paxsh
,尽管我怀疑它是否会像它的祖先一样流行:-)

运行它,您可以看到它的运行:

pax> mv bash paxsh

pax> ./paxsh --version
GNU bash, version 4.2-pax.0(1)-release (i686-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

pax> ./paxsh

pax> echo $BASH_VERSION
4.2-pax.0(1)-release

pax> echo "[$PS1]"
[pax> ]

pax> echo "[$(evalps1)]"
[pax> ]

pax> PS1="\h: "

paxbox01: echo "[$PS1]"
[\h: ]

paxbox01: echo "[$(evalps1)]"
[paxbox01: ]
pax>mv bash-paxsh
pax>/paxsh--版本
GNUBash,版本4.2-pax.0(1)-发行版(i686 pc linux GNU)
版权所有(C)2011免费软件基金会。
许可证GPLv3+:GNU GPL版本3或更高版本
这是自由软件;您可以自由更改和重新发布它。
在法律允许的范围内,不存在任何担保。
pax>/paxsh
pax>echo$BASH\u版本
4.2-pax.0(1)-释放
pax>echo“[$PS1]”
[和平>]
pax>echo“[$(evalps1)]”
[和平>]
pax>PS1=“\h:”
paxbox01:echo“[$PS1]”
[\h:]
paxbox01:echo“[$(evalps1)]”
[paxbox01:]
当您将其中一个
PSx
变量放入提示符时,回显
$PS1
只会给出该变量,而
evalps1
命令会对其求值并输出结果


现在,诚然,对
bash
进行代码更改以添加内部命令可能被一些人认为是过火了,但是,如果您想要对
PS1
进行完美的评估,这当然是一个选项。

还有一种可能性:不编辑bash源代码,使用
script
实用程序(ubuntu上的
bsdutils
包的一部分):

$TEST\u PS1=“\e[31;1m\u@\h:\n\e[0;1m\$\e[0m”
$RANDOM\u STRING=此处的某个\u RANDOM\u STRING\u不是\u PS1的\u部分

$script/dev/null我喜欢修复Bash使其更好的想法,我也很欣赏paxdiablo关于如何修补Bash的文章。我以后会尝试一下

但是,在不修补Bash源代码的情况下,我有一个单行程序的hack,它既可移植又不复制功能,因为该解决方案只使用Bash及其内置代码

x="$(PS1=\"$PS1\" echo -n | bash --norc -i 2>&1)"; echo "'${x%exit}'"
请注意,
tty
stdio
出现了一些奇怪的情况,因为这同样有效:

x="$(PS1=\"$PS1\" echo -n | bash --norc -i 2>&1 > /dev/null)"; echo "'${x%exit}'"
因此,尽管我不明白stdio在这里发生了什么,但我的黑客在Bash 4.2,NixOS GNU/Linux上为我工作。修补Bash源代码肯定是一个更优雅的解决方案,而且现在使用Nix应该非常容易和安全。

两个答案:“纯Bash”和“Bash+sed” 简介 当然,从的4.4版开始,您必须正确地使用参数转换:

请参见
manless\+/parameter\\\transformation bash

但对于较旧的bash,甚至只是为了玩字符串和变量

使用
bash
+
sed
这是我的窍门:

ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1 |
              sed ':a;$!{N;ba};s/^\(.*\n\)*\(.*\)\n\2exit$/\2/p;d')"
然后将执行
sed
命令

  • 将所有行放入一个缓冲区(
    :a;$!{N;ba};
    ),然后
  • s/^\(.*\n\)*\(.*\)\n\2exit$/\2/
    )替换
    行出口的末尾。
    
    • 其中
      变成
      \1
    • 成为
      \2
测试用例: (最后一行以绿色打印
ubuntu
@
$
以黑色打印,路径(
/tmp
)以蓝色打印)

使用多行提示进行快速测试:

ExpPS1="$(bash --rcfile <(echo "PS1='Test string\n$(date)\n$PS1'"
    ) -i <<<'' 2>&1)";
mapfile ExpPS1 <<<"${ExpPS1%exit}"
ExpPS1=( "${ExpPS1[*]::${#ExpPS1[@]}/2}" )    

echo ${ExpPS1@Q}
$'Test string\r\n Sat Jan 9 19:23:47 CET 2021\r\n \E]0;ubuntu@ubuntu: ~\aubuntu@ubuntu:~$ \n'

ExpPS1=“$(bash--rcfile由于bash 4.4,您可以使用
x="$(PS1=\"$PS1\" echo -n | bash --norc -i 2>&1)"; echo "'${x%exit}'"
x="$(PS1=\"$PS1\" echo -n | bash --norc -i 2>&1 > /dev/null)"; echo "'${x%exit}'"
ExpPS1=${PS1@P}
echo ${ExpPS1@Q}
$'\001\E]0;user@host: ~\a\002user@host:~$ '
ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1 |
              sed ':a;$!{N;ba};s/^\(.*\n\)*\(.*\)\n\2exit$/\2/p;d')"
user@host:~$ 
user@host:~$ exit
while ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1 |
          sed ':a;$!{N;ba};s/^\(.*\n\)*\(.*\)\n\2exit$/\2/p;d')"
    read -rp "$ExpPS1" && [ "$REPLY" != exit ] ;do
    eval "$REPLY"
  done
ubuntu@ubuntu:~$ cd /tmp
ubuntu@ubuntu:/tmp$ PS1="${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$ "
ubuntu@ubuntu:/tmp$ 
ubuntu@ubuntu:/tmp$ exit
ubuntu@ubuntu:/tmp$ od -A n -t c <<< $ExpPS1 
 033   [   1   ;   3   2   m   u   b   u   n   t   u 033   [   0
   m   @ 033   [   1   ;   3   2   m   u   b   u   n   t   u 033
   [   0   m   : 033   [   1   ;   3   4   m   ~ 033   [   0   m
   $  \n
ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1)"
mapfile ExpPS1 <<<"${ExpPS1%exit}"
ExpPS1=( "${ExpPS1[*]::${#ExpPS1[@]}/2}" )
declare -p ExpPS1
declare -a ExpPS1=([0]=$'\E]0;ubuntu@ubuntu: ~\aubuntu@ubuntu:~$ \n')
echo ${ExpPS1@Q}
$'\E]0;ubuntu@ubuntu: ~\aubuntu@ubuntu:~$ \n'
ExpPS1="$(bash --rcfile <(echo "PS1='Test string\n$(date)\n$PS1'"
    ) -i <<<'' 2>&1)";
mapfile ExpPS1 <<<"${ExpPS1%exit}"
ExpPS1=( "${ExpPS1[*]::${#ExpPS1[@]}/2}" )    

echo ${ExpPS1@Q}
$'Test string\r\n Sat Jan 9 19:23:47 CET 2021\r\n \E]0;ubuntu@ubuntu: ~\aubuntu@ubuntu:~$ \n'
od -A n -t c  <<<${ExpPS1}
   T   e   s   t       s   t   r   i   n   g  \r  \n       S   a
   t       J   a   n           9       1   9   :   2   6   :   3
   9       C   E   T       2   0   2   1  \r  \n     033   ]   0
   ;   u   b   u   n   t   u   @   u   b   u   n   t   u   :    
   ~  \a   u   b   u   n   t   u   @   u   b   u   n   t   u   :
   ~   $      \n  \n
ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1)"
mapfile ExpPS1 <<<"${ExpPS1%exit}"
[ "${ExpPS1[*]::${#ExpPS1[@]}/2}" = "${ExpPS1[*]: -${#ExpPS1[@]}/2}" ] ||
    echo WARNING: First half seem not match last half string.
ExpPS1=( "${ExpPS1[*]::${#ExpPS1[@]}/2}" )
read -r myprompt <<'EOF'
${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$(__git_ps1 ' (%s)')$ 
EOF
$ printf '%s\n' "${myprompt@P}"
gniourf@rainbow:~$
$
read -e -p "${myprompt@P}"
myprompt=${myprompt@P}
printf '%s\n' "${myprompt//[$'\001'$'\002']}"
( set +o emacs +o vi; printf '%s\n' "${myprompt@P}" )