回声扩大PS1

我有一个shell脚本,在几个目录( fgit )中运行相同的命令。 对于每个目录,我希望它显示当前提示+将在那里运行的命令。 如何获得解码 (扩展)的PS1对应的string? 例如,我的默认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只评估颜色。

开放源代码软件的一个很大的优势就是源代码可以打开:-)

Bash本身并不提供这种function,但是可以使用各种技巧来提供一个子集(比如用$USERreplace\u $USER等等)。 但是,这需要大量的function重复,并确保代码与未来的任何bash保持同步。

如果你想获得提示variables的所有权力(你不介意让一些代码弄脏你的手(如果你想知道,为什么你在这里?)),很容易添加到壳本身。

如果你下载bash的代码(我在看4.2版本),那么就有一个包含decode_prompt_string()函数的decode_prompt_string()文件:

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

这是评估提示的PSxvariables的函数。 为了允许这个function被提供给shell本身的用户(而不是仅仅 shell使用),你可以按照下面的步骤添加一个内部命令evalps1

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

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

其次,更改`builtins / Makefile.in来添加一个新的源文件。 这需要一些步骤。

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

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

(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修改了它), exit.def用一个非常简单的函数来获取和解码PS1

最后,只要在顶层目录中build立这个东西:

 ./configure make 

出现的bash可执行文件可以重命名为paxsh ,尽pipe我怀疑它会像它的祖先一样普遍:-)

运行它,你可以看到它的行动:

 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: ] 

当你把一个PSxvariables放到提示符中时, PSx $PS1只给你一个variables,而evalps1命令对它进行求值并输出结果。

现在,授予,更改为bash代码添加一个内部命令可能会被认为是一个过度杀伤力,但是,如果你想PS1的完美评估,这当然是一个select。

你为什么不自己处理$PS1转义replace呢? 一系列替代如:

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

顺便说一句,zsh有能力解释即时逃脱。

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

要么

 p=${(%%)PS1} 

由于Bash 4.4你可以使用@P扩展:

首先我把你的提示string放在一个variablesmyprompt使用read -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字符,您在这里看不到,但是如果您尝试编辑这篇文章,您可以看到它们;您也可以在您的terminal如果您键入命令)。


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

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

这将提示用户正确解释myprompt

对于这篇文章,Greg Wooledge回答说,你可以从string中去掉\001\002 。 这可以这样来实现:

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

对于这篇文章,Chet Ramey回答说,你也可以使用set +o emacs +o viclosures行编辑。 所以这也会做:

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

我喜欢修复Bash使其更好的想法,我赞赏paxdiablo关于如何修补Bash的详细答案 。 我会去的。

但是,如果没有修补Bash源代码,我有一个既可移植又不重复function的单行线破解,因为解决方法只使用Bash及其内build函数。

 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}'" 

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

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

由于使用sed做这个更简单,第一个答案将使用sed 。

请参阅下面的纯bash解决scheme。

bash提示扩展, 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>exitreplace<everything, terminated by end-of-line><prompt>end-of-line<prompt>exit 。 ( s/^\(.*\n\)*\(.*\)\n\2exit$/\2/ )。
    • 其中<everything, terminated by end-of-line>成为\1
    • <prompt>变成\2

testing用例:

 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@ ubuntu$黑色和path( /tmp )蓝色)

 ubuntu@ubuntu:/tmp$ exit ubuntu@ubuntu:/tmp$ od -A n -tc <<< $ExpPS1 033 [ 1 ; 3 2 mubuntu 033 [ 0 m @ 033 [ 1 ; 3 2 mubuntu 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循环来确保正确处理多行提示:

用下列代码replace第一行

 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 -tc <<<${ExpPS1} T eststring \r T ue M ay 1 0 1 1 : 0 4 : 5 4 UTC 2 0 1 6 \r 033 ] 0 ; u buntu @ ubuntu : ~ \a ubuntu @ ubuntu : ~ $ \n 

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

还有一种可能性:不用编辑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命令生成一个指定的文件&输出也显示在标准输出。 如果省略了filename,它将生成一个名为typescript的文件。

由于在这种情况下我们对日志文件不感兴趣,文件名被指定为/dev/null 。 相反,脚本命令的stdout被传递给awk进行进一步处理。

  1. 整个代码也可以被封装成一个函数。
  2. 而且,输出提示也可以分配给一个variables。
  3. 这种方法也支持parsingPROMPT_COMMAND