我有一个 shell 脚本,它在多个目录中运行相同的命令(弗吉特)。对于每个目录,我希望它显示当前的提示符+将在那里运行的命令。如何获取对应的字符串 已解码 (扩展)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 仅评估颜色。

有帮助吗?

解决方案

的开放源码软件的一个很大的优点是,源是,井,开: - )

击本身不提供此功能,但也有可用于提供一个子集的各种技巧(如代与\u$USER)。然而,这需要很多的功能重复并确保代码保持同步在将来任何bash确实

如果你想获得的所有的提示变量(你不介意让你的手脏有位编码(如果你很介意,你为什么在这里的力量? )),它是很容易添加到外壳本身。

如果您下载bash(我看4.2版本)的代码,有一个y.tab.c文件,其中包含了decode_prompt_string()功能:

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

这是用于评估PSx变量用于提示功能。为了允许这种功能被提供给壳本身的用户(而不是仅仅使用的通过壳),可以按照以下步骤来添加一个内部命令evalps1

首先,改变support/mkversion.sh所以你不会有“真正的” bash混淆,并且使得FSF可以拒绝所有的知识进行保修目的:-)简单地改变一行(我加了-pax位):

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

其次,改变builtins/Makefile.in添加新的源文件。这需要许多步骤。

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

(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的完美评价,它肯定是一个选项。

其他提示

由于击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人物,从\[\]未来,你不能看到这里,但你可以看到他们,如果你尝试编辑这个职位,你还会看到他们在你的终端如果键入命令)。


要摆脱这些,由丹尼斯·威廉姆森bash的邮件列表发送的诀窍是使用read -e -p使这些字符获取由readline库解释:

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

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

要这个职位,格雷格Wooledge回答,你可能也只是剥离字符串中的\001\002。这可以像这样来实现:

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

要这个职位,切特·拉梅回答,你也可以关闭符合set +o emacs +o vi共编辑。因此,这将做太多:

( set +o emacs +o vi; printf '%s\n' "${myprompt@P}" )

你为什么不只是处理$PS1逃生替代自己呢?一系列取代诸如这些的:

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

顺便提及,zsh的有权解释提示逃逸的能力。

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

p=${(%%)PS1}

两个答案:“纯 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

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

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

$ 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...
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top