题
我有一个 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.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
的完美评价,它肯定是一个选项。
其他提示
由于击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}
我喜欢固定击,使之更好的想法,我很欣赏paxdiablo的详细解答有关如何修补猛砸一>。我有一展身手的某个时候。
然而,如果没有修补击源代码,我有一个单行的入侵,是便携式和不重复的功能,因为该解决方法的用途仅击及其建宏。
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
,我劈为我工作在猛砸4.2,NixOS GNU / Linux操作系统。修补Bash的源代码,绝对是一个更优雅的解决方案,它应该是很容易和安全,现在这样做,我使用的尼克斯。
两个答案:“纯 bash”和“bash + sed”
通过使用这样做 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 进行进一步处理。
- 整个代码也可以封装成一个函数。
- 此外,输出提示也可以分配给变量。
- 该方法还支持解析
PROMPT_COMMAND
...