回声扩大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,但是可以使用各种技巧来提供一个子集(比如用$USER
replace\u
$USER
等等)。 但是,这需要大量的function重复,并确保代码与未来的任何bash
保持同步。
如果你想获得提示variables的所有权力(你不介意让一些代码弄脏你的手(如果你想知道,为什么你在这里?)),很容易添加到壳本身。
如果你下载bash
的代码(我在看4.2版本),那么就有一个包含decode_prompt_string()
函数的decode_prompt_string()
文件:
char *decode_prompt_string (string) char *string; { ... }
这是评估提示的PSx
variables的函数。 为了允许这个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: ]
当你把一个PSx
variables放到提示符中时, 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 vi
closures行编辑。 所以这也会做:
( 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>exit
replace<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进行进一步处理。
- 整个代码也可以被封装成一个函数。
- 而且,输出提示也可以分配给一个variables。
- 这种方法也支持parsing
PROMPT_COMMAND
…