最近看了TJ大神的node版本管理工具——n的源码(shell编写),收获颇多。我根据自己的需求编写了一个叫做p
的工具,默认给出了bash、zsh提示符的多个主题,可以通过终端中的图形界面手动进行选择(相当于动态修改PS1变量)。这个工具开始是用shell实现,基本功能完成后,很多细节无法处理好,所以又用python写了一个比较完善的实现。
这是该工具的demo(动态gif):
p的下载地址:https://github.com/someus/p。
下面分析下它的实现。
如何修改bash的提示符
很简单,修改PS1变量就行了。不过为了让字体有颜色、显示用户名等,需要加一些特殊的字符。
我们看一下依次执行下面两个赋值语句的效果:
PS1="% "
PS1="\[\e[1;32m\][\W]\$\[\e[0m\] "
\[\e[1;32m\]
中1
代表粗体,32
代表绿色。\[\e[0m\]
则是重置颜色的意思。\e
是escape的意思,有时候会写成\033
,这是8进制,正好和ASCII表中的escape字符对应。
提示符支持很多颜色,例如31代表红色、33代表黄色,等等。
\W
是当前路径,一般还会使用\u
、\h
等。\u
是用户名,\h
是主机名,。
支持的转义字符有很多:
\a an ASCII bell character (07)
\d the date in "Weekday Month Date" format (e.g., "Tue May 26")
\D{format} the format is passed to strftime(3) and the result
is inserted into the prompt string an empty format
results in a locale-specific time representation.
The braces are required
\e an ASCII escape character (033)
\h the hostname up to the first `.'
\H the hostname
\j the number of jobs currently managed by the shell
\l the basename of the shell's terminal device name
\n newline
\r carriage return
\s the name of the shell, the basename of $0 (the portion following
the final slash)
\t the current time in 24-hour HH:MM:SS format
\T the current time in 12-hour HH:MM:SS format
\@ the current time in 12-hour am/pm format
\A the current time in 24-hour HH:MM format
\u the username of the current user
\v the version of bash (e.g., 2.00)
\V the release of bash, version + patch level (e.g., 2.00.0)
\w the current working directory, with $HOME abbreviated with a tilde
\W the basename of the current working directory, with $HOME
abbreviated with a tilde
\! the history number of this command
\# the command number of this command
\$ if the effective UID is 0, a #, otherwise a $
\nnn the character corresponding to the octal number nnn
\\ a backslash
\[ begin a sequence of non-printing characters, which could be used
to embed a terminal control sequence into the prompt
\] end a sequence of non-printing characters
颜色支持和转义字符可以参考:
Color Bash Prompt - ArchLinux wiki
如何修改zsh的提示符
可以修改PS1,也可以修改PROMPT变量,不过颜色表示和转移字符都有所不同。另外,必须在配置文件~/.zshrc
中加入下面的内容:
autoload -U colors && colors
看一下下面的赋值语句的效果:
PS1="$ "
PROMPT="%{$fg_no_bold[yellow]%}%1~ %{$reset_color%}%# "
具体可以参考:
终端中如何进入全屏
先看下shell该怎么做:
#!/usr/bin/env bash
enter_fullscreen() {
tput smcup # Save the display
stty -echo # 不回显输入的字符
}
leave_fullscreen() {
tput rmcup # Restore the display
stty echo # 回显输入的字符
}
enter_fullscreen
echo "已经进入全屏"
sleep 4
leave_fullscreen
上面定义的函数中enter_fullscreen
用于进入全屏,leave_fullscreen
用于退出全屏。
运行上面的脚本,可以看到:
基于上面的代码,我们可以把各种效果的提示符打印出来:
#!/usr/bin/env bash
enter_fullscreen() {
tput smcup # Save the display
stty -echo # 不回显输入的字符
}
leave_fullscreen() {
tput rmcup # Restore the display
stty echo # 回显输入的字符
}
enter_fullscreen
echo -e "$ "
echo -e "\e[1;32m[/home]\$\e[0m "
sleep 4
leave_fullscreen
有相同作用的python代码是:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import time
import os
def enter_fullscreen():
print '\033[?1049h\033[H'
os.system("stty -echo")
def leave_fullscreen():
print '\033[?1049l'
os.system("stty echo")
enter_fullscreen()
print "$ "
print "\033[1;32m[/home]\$\033[0m "
time.sleep(4)
leave_fullscreen()
注意,\e
替换成了\033
。
检测方向键和回车的输入
这个功能要求程序可以监测上下方向键、回车键的敲击事件。
shell的实现:
#!/usr/bin/env bash
UP=$'\033[A'
DOWN=$'\033[B'
stty -echo # 关闭输入字符的回显
while true; do
read -n 3 c
case "$c" in
$UP)
echo "上"
;;
$DOWN)
echo "下"
;;
"")
echo "回车"
break
;;
esac
done
stty echo # 开启输入字符的回显
运行效果:
python的实现:
# -*- coding: UTF-8 -*-
import termios, fcntl, sys, os
UP = '\033[A'
DOWN = '\033[B'
fd = sys.stdin.fileno()
oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
try:
while 1:
try:
c = sys.stdin.read(3)
if c == UP:
print '上'
elif c == DOWN:
print '下'
elif c == '\n':
print '回车'
break
except IOError: pass
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
运行效果和上面类似。
如何储存提示符的字符串值
使用键值对或者二维数组存储,提示符字符串值和样例需要成对出现。对于键值对,python中可以用字典,shell可以用关联数组(bash必须是版本4或者以上)。
以python为例,可以这样编写:
COLORS = dict(
txtblk='\033[0;30m', # Black - Regular
txtred='\033[0;31m', # Red
txtgrn='\033[0;32m', # Green
txtylw='\033[0;33m', # Yellow
txtblu='\033[0;34m', # Blue
txtpur='\033[0;35m', # Purple
txtrst='\033[0m' # Text Reset - Reset
)
PROMPT_BASH = (
dict(
prompt = '\[{txtgrn}\]\$ \[{txtrst}\]'.format(**COLORS),
example = '{txtgrn}$ {txtrst}ls -l'.format(**COLORS)
),
dict(
prompt = '\[{txtcyn}\]\$ \[{txtrst}\]'.format(**COLORS),
example = '{txtcyn}$ {txtrst}ls -l'.format(**COLORS)
),
)
p的命令行参数该怎么实现
以python为例子。
由于参数比较简单,就没用用argparse等模块。直接使用的sys.argv
:
if len(sys.argv[1:]) == 0:
help()
return
for arg in sys.argv[1:]:
if arg in ('-h', '--help'):
help()
return
elif arg in ('-v', '--version'):
version()
return
elif arg in ('--patch', ):
patch_config()
return
else:
select_prompt(arg)
return
现在,可以实现p了
假如当前使用的是bash。下面通过某次对p的使用说明p的主要流程:
对每个bash提示符编号,设置当前选中的编号为0
(也就是第一个提示符),然后进入全屏,显示所有的提示符号的样例。被选中的提示符的样例在显示时需要加一个特殊的标记,便于用户区分。(p中使用的是箭头“->”)。
当按下方向键
时候,设置当前选中的编号为1
,clear屏幕,重新显示所有的提示符样例。
接着又按了下方向键
,设置当前选中的编号为2
,clear屏幕,重新显示所有的提示符样例。
然后又按了上方向键
,设置当前选中的编号为1
,clear屏幕,重新显示所有的提示符样例。
最后按了回车
,由于当前选中的编号是1
,我们将1
对应的提示符的信息写入~/.p
,内容可能是:
export PS1="\[\e[1;32m\][\W]\$\[\e[0m\] "
现在,手动source ~/.p
即可。
但是为了方便,我们在~/.bashrc
中加入:
alias p="p && source $HOME/.p"
这样,开启新的终端后,运行p之后就不必手动source ~/.p
了。
如果要使用原先的命令p
,而非alias的p
,可以:
$ \p
p的安装和使用
上面将的是p的主要实现,当前p的实现要更完善,所以实际使用中会略有不同。
1、下载源码
在**https://github.com/someus/p**下载p的源码包,解压。
2、安装
$ sudo make install
默认安装到/usr/local/bin/p
。
3、在bash、zsh配置文件中加入alias等信息
$ \p --patch
目前配置不会在当前的终端生效。在一个新启动的bash或者zsh中会生效。
4、运行p
在一个新启动的bash或者zsh中,运行命令p。
参考
Color Bash Prompt - Archlinux wiki
How can I change the color of my prompt in zsh?
ANSI escape code - wikipedia
How do I get a single keypress at a time? - python docs
Terminal control/Preserve screen
Python method for reading keypress?