我有一个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
只评估颜色。
答案 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}'"
请注意,tty
和stdio
看到了一些奇怪的事情,因为这也有效:
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)
使用sed
执行此操作更为简单,第一个答案将使用sed。
请参阅下面的纯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
替换为<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以进行进一步处理。
PROMPT_COMMAND
...