Echo扩展了PS1

时间:2010-08-10 18:05:27

标签: bash ps1

我有一个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只评估颜色。

7 个答案:

答案 0 :(得分:18)

自Bash 4.4起,您可以使用@P扩展:

首先,我使用myprompt将您的提示字符串放在变量read -r中,并引用此处-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字符,来自\[\],你在这里看不到,但你可以看到它们如果您尝试编辑此帖子;如果您输入命令,您还会在终端中看到它们。)

为了摆脱这些,Dennis Williamson在bash邮件列表上发送的技巧是使用read -e -p,以便这些字符被readline库解释:

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

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

对于这篇文章,Greg Wooledge回答说你也可以从字符串中删除\001\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}" )

答案 1 :(得分:11)

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

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

如果你想获得所有提示变量的强大功能(你不介意用一些编码来弄脏你的手(如果你介意的话,为什么你在这里?) )),它很容易添加到shell本身。

如果您下载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.def添加到DEFSRC的末尾。

(b)将evalps1.o添加到OFILES的末尾。

(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进行完美评估,那么它肯定是一种选择。< / p>

答案 2 :(得分:9)

为什么不亲自处理$PS1逃逸替换?一系列的替换如下:

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

顺便说一下,zsh能够解释提示转义。

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

p=${(%%)PS1}

答案 3 :(得分:4)

我喜欢修复Bash以使其更好的想法,我感谢paxdiablo的verbose answer如何修补Bash。我有时会去。

但是,如果没有修补Bash源代码,我有一个单行黑客,它既可移植又不重复功能,因为解决方法只使用Bash及其内置。

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

请注意,ttystdio看到了一些奇怪的事情,因为这也有效:

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

所以虽然我不明白这里的stdio是怎么回事,但我的黑客在Bash 4.2,NixOS GNU / Linux上为我工作。修补Bash源代码绝对是一个更优雅的解决方案,现在使用Nix应该非常简单和安全。

答案 4 :(得分:2)

两个回答:“Pure bash”和“bash + sed”

使用sed执行此操作更为简单,第一个答案将使用

请参阅下面的 解决方案。

提示展开,bash + sed

有我的黑客:

ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1 |
              sed ':;$!{N;b};s/^\(.*\n\)*\(.*\)\n\2exit$/\2/p;d')"

说明:

正在运行bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1

可能会返回类似的内容:

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@ubuntu:~$ 
ubuntu@ubuntu:~$ exit

sed命令将

  • 将所有行放入一个缓冲区(:;$!{N;b};),而不是
  • <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 ':;$!{N;b};s/^\(.*\n\)*\(.*\)\n\2exit$/\2/p;d')"
    read -rp "$ExpPS1" && [ "$REPLY" != exit ] ;do
    eval "$REPLY"
  done

从那里开始,你处于一种伪交互式shell(没有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)"
ExpPS1_W="${ExpPS1%exit}"
ExpPS1="${ExpPS1_W##*$'\n'}"
ExpPS1_L=${ExpPS1_W%$'\n'$ExpPS1}
while [ "${ExpPS1_W%$'\n'$ExpPS1}" = "$ExpPS1_W" ] ||
      [ "${ExpPS1_L%$'\n'$ExpPS1}" = "$ExpPS1_L" ] ;do
    ExpPS1_P="${ExpPS1_L##*$'\n'}"
    ExpPS1_L=${ExpPS1_L%$'\n'$ExpPS1_P}
    ExpPS1="$ExpPS1_P"$'\n'"$ExpPS1"
  done

需要while循环以确保正确处理多行提示:

将第一行替换为:

ExpPS1="$(bash --rcfile <(echo "PS1='${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$ '") -i <<<'' 2>&1)"

ExpPS1="$(bash --rcfile <(echo "PS1='Test string\n$(date)\n$PS1'") -i <<<'' 2>&1)";

最后多行将打印:

echo "$ExpPS1"
Test string
Tue May 10 11:04:54 UTC 2016
ubuntu@ubuntu:~$ 

od -A n -t c  <<<${ExpPS1}
   T   e   s   t       s   t   r   i   n   g  \r       T   u   e
       M   a   y       1   0       1   1   :   0   4   :   5   4
       U   T   C       2   0   1   6  \r     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

答案 5 :(得分:1)

您可能必须编写一个小型C程序,它使用与bash相同的代码(是库调用吗?)来显示该提示,并只调用C程序。当然,这不是很便携,因为你必须在每个平台上编译它,但这是一个可能的解决方案。

答案 6 :(得分:1)

还有一种可能性:无需编辑bash源代码,使用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 prompt properly here>

script命令生成指定的文件&amp;输出也显示在stdout上。如果省略filename,则会生成一个名为typescript的文件。

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

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