26

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

${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)')$

我想username@hostname:/path$用漂亮的颜色来呼应生成的提示,最好(但不一定)。粗略地看一下 Bash 手册并没有给出任何明确的答案,echo -e $PS1只是评估了颜色。

4

7 回答 7

23

从 Bash 4.4 开始,您可以使用@P扩展:

首先,我将您的提示字符串放入一个变量mypromptread -r,并使用引用的 here-doc:

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

要打印提示(如果它会被解释PS1),请使用扩展${myprompt@P}

$ printf '%s\n' "${myprompt@P}"
gniourf@rainbow:~$
$

(事实上​​,有一些\001字符\002来自这里\[\]你在这里看不到,但是如果你尝试编辑这篇文章,你可以看到它们;如果你输入命令,你也会在终端中看到它们)。


为了摆脱这些,丹尼斯威廉姆森在 bash 邮件列表中发送的技巧是使用read -e -p这些字符由 readline 库解释:

read -e -p "${myprompt@P}"

这将提示用户,并myprompt正确解释。

在这篇文章中,Greg Wooledge 回答说你不妨从字符串中去掉\001and 。\002这可以像这样实现:

myprompt=${myprompt@P}
printf '%s\n' "${myprompt//[$'\001'$'\002']}"

在这篇文章中,Chet Ramey 回答说您也可以使用set +o emacs +o vi. 所以这也可以:

( set +o emacs +o vi; printf '%s\n' "${myprompt@P}" )
于 2016-05-10T12:02:41.400 回答
12

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

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

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

如果您下载bash(我正在查看版本 4.2)的代码,则有一个y.tab.c包含该decode_prompt_string()函数的文件:

char *decode_prompt_string (string) char *string; { ... }

这是评估PSx提示变量的函数。为了允许向 shell 本身的用户提供此功能(而不仅仅是shell 使用),您可以按照以下步骤添加内部命令evalps1

首先,进行更改,support/mkversion.sh以免您将其与“真实”混淆bash,并且 FSF 可以出于保修目的否认所有知识 :-) 只需更改一行(我添加了-pax一点):

echo "#define DISTVERSION \"${float_dist}-pax\""

二、更改builtins/Makefile.in添加新的源文件。这需要许多步骤。

(a) 添加$(srcdir)/evalps1.defDEFSRC.

(b) 添加evalps1.oOFILES.

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

evalps1.o: evalps1.def $(topdir)/bashtypes.h $(topdir)/config.h \
           $(topdir)/bashintl.h $(topdir)/shell.h common.h

第三,添加builtins/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;
}

其中大部分是 GPL 许可证(因为我从 修改它exit.def),最后有一个非常简单的函数来获取和解码PS1

最后,只需在顶级目录中构建东西:

./configure
make

出现的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: ]

当您将其中一个PSx变量放入提示符时,回显$PS1只是为您提供变量,而evalps1命令对其进行评估并输出结果。

现在,当然,对代码进行更改以bash添加内部命令可能会被某些人认为是矫枉过正,但是,如果您想要对 进行完美评估PS1,这当然是一种选择。

于 2013-01-09T09:29:17.247 回答
9

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

p="${PS1//\\u/$USER}"; p="${p//\\h/$HOSTNAME}"

顺便说一句,zsh 具有解释提示转义的能力。

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

或者

p=${(%%)PS1}
于 2010-08-10T20:37:41.473 回答
5

我喜欢修复 Bash 以使其更好的想法,我很欣赏 paxdiablo关于如何修补 Bash的详细回答。有时间我会去的。

但是,在不修补 Bash 源代码的情况下,我有一个既可移植又不重复功能的单行 hack,因为该解决方法仅使用 Bash 及其内置函数。

x="$(PS1=\"$PS1\" echo -n | bash --norc -i 2>&1)"; echo "'${x%exit}'"

请注意,tty's 发生了一些奇怪的事情,并且stdio看到这也有效:

x="$(PS1=\"$PS1\" echo -n | bash --norc -i 2>&1 > /dev/null)"; echo "'${x%exit}'"

所以虽然我不明白stdio这里发生了什么,但我的 hack 在 Bash 4.2、NixOS GNU/Linux 上为我工作。修补 Bash 源代码绝对是一个更优雅的解决方案,而且现在我正在使用 Nix,它应该非常容易和安全。

于 2014-06-03T03:55:54.467 回答
3

两个答案:“纯 bash”和“bash + sed”

介绍

当然,从 4.4 版开始,由于gniourf_gniourf正确回答,您必须使用参数转换

ExpPS1=${PS1@P}
echo ${ExpPS1@Q}
$'\001\E]0;user@host: ~\a\002user@host:~$ '

man -Pless\ +/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')"

解释:

跑步bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1

可能会返回如下内容:

user@host:~$ 
user@host:~$ exit

然后该sed命令将

  • 将所有行放入一个缓冲区 ( :a;$!{N;ba};),然后
  • 替换<everything, terminated by end-of-line><prompt>end-of-line<prompt>exit<prompt>。( s/^\(.*\n\)*\(.*\)\n\2exit$/\2/)。
    • <everything, terminated by end-of-line>变成哪里\1
    • <prompt>成为\2.

测试用例:

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

从那里,您处于一种伪交互式外壳中(没有 readline 设施,但这没关系)......

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以绿色、@:黑色打印$ ,路径 ( /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}" )
于 2016-05-10T09:11:07.973 回答
1

另一种可能性:无需编辑 bash 源代码,使用script实用程序(bsdutilsubuntu 上的包的一部分):

$ 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 prompt properly here>

script命令生成指定的文件,输出也显示在标准输出上。如果省略文件名,它会生成一个名为 typescript 的文件。

由于在这种情况下我们对日志文件不感兴趣,因此将文件名指定为/dev/null. 而是将脚本命令的标准输出传递给 awk 以进行进一步处理。

  1. 整个代码也可以封装成一个函数。
  2. 此外,也可以将输出提示分配给变量。
  3. 这种方法还支持解析PROMPT_COMMAND...
于 2014-03-11T11:36:34.250 回答
0
  1. ps=${ps@P}用(bash 4.4)扩展它
  2. 删除\x01and之间\x02(由 bash 替换\[and\]占位符创建。
  3. 检查剩下的所有字符
ps1_size(){
  # Ref1: https://stackoverflow.com/questions/3451993/how-to-expand-ps1
  >&2 echo -e "\nP0: Raw"
  local ps=$PS1
  echo -n "$ps" | xxd >&2 

  >&2 echo -e "\nP1: Expanding (require bash 4.4)"
  ps=${ps@P}
  echo -n "$ps" | xxd >&2 

  >&2 echo -e "\nP2: Removing everything 01 and 02"
  shopt -s extglob
  ps=${ps//$'\x01'*([^$'\x02'])$'\x02'}
  echo -n "$ps" | xxd >&2 

  >&2 echo -e "\nP3: Checking"
  if [[ "$ps" =~ [\x07\x1b\x9c] ]]; then
    # Check if escape inside
    # 07 => BEL
    # 1b => ESC
    # 9C => ST
    >&2 echo 'Warning: There is an escape code in your PS1 which is not betwwen \[ \]'
    >&2 echo "Tip: put \[ \] around your escape codes (ctlseqs + associated parameters)"
    echo -n "$ps" | xxd >&2
  # Check printable characters <= 20 .. 7e, and newline
  # -- Remove the trailing 0x0a (BEL)
  elif [[ "$ps" =~ [^[:graph:][:space:]] ]]; then
    >&2 echo 'Warning: There is a non printable character in PS1 which is not between \[ \]'
    >&2 echo "Tip: put \[ \] around your escape codes (ctlseqs + associated parameters)"
    echo "$ps"
    echo -n "$ps" | xxd >&2 
  fi

  # Echo result
  echo -n "${#ps}"
}

ps1_size

应该输出如下内容:

~/Software/Bash/Mouse (master)$ source ../ps1_size.sh

P0: Raw
00000000: 5c5b 5c65 5d30 3b60 7061 7273 655f 7469  \[\e]0;`parse_ti
00000010: 746c 6560 5c30 3037 5c5d 5c5b 5c65 5b33  tle`\007\]\[\e[3
00000020: 326d 5c5d 5c77 205c 5b5c 655b 3333 6d5c  2m\]\w \[\e[33m\
00000030: 5d60 7061 7273 655f 6769 745f 6272 616e  ]`parse_git_bran
00000040: 6368 605c 5b5c 655b 306d 5c5d 2420       ch`\[\e[0m\]$

P1: Expanding (require bash 4.4)
00000000: 011b 5d30 3b7e 2f53 6f66 7477 6172 652f  ..]0;~/Software/
00000010: 4261 7368 2f4d 6f75 7365 0702 011b 5b33  Bash/Mouse....[3
00000020: 326d 027e 2f53 6f66 7477 6172 652f 4261  2m.~/Software/Ba
00000030: 7368 2f4d 6f75 7365 2001 1b5b 3333 6d02  sh/Mouse ..[33m.
00000040: 286d 6173 7465 7229 011b 5b30 6d02 2420  (master)..[0m.$

P2: Removing everything 01 and 02
00000000: 7e2f 536f 6674 7761 7265 2f42 6173 682f  ~/Software/Bash/
00000010: 4d6f 7573 6520 286d 6173 7465 7229 2420  Mouse (master)$

P3: Checking
32~/Software/Bash/Mouse (master)$

如果存在一些控制字符,您可以按照此stackoverflow 中的说明将其删除:Removing ANSI color codes from text stream。我使用以下内容删除了github 中的 SCI 和 OSC: mouse_xterm

  # Sanitize, in case
  ps=$(LC_ALL=C sed '
    # Safety
    s/\x01\|\x02//g;
    # Safety Remove OSC https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
    # 20 .. 7e => printable characters
    # 07 => BEL
    # 9C => ST
    # 1b 5C => ESC + BS
    s/\x1b\][0-9;]*[\x20-\x7e]*\([\x07\x9C]\|\x1b\\\)//g;
    # Safety: Remove all escape sequences https://superuser.com/questions/380772/removing-ansi-color-codes-from-text-stream
    s/\x1b\[[0-9;]*[a-zA-Z]//g;
  ' <<< "$ps")
于 2021-12-29T20:26:44.010 回答