Bash 编程高级教程(四)
协议:CC BY-NC-SA 4.0
十四、编写屏幕脚本
Unix 纯粹主义者会对这一章摇头。传统上,屏幕操作是通过termcap或terminfo数据库来完成的,这些数据库提供了操作任何几十种甚至上百种终端所需的信息。数据库的 shell 接口是一个外部命令,tput。
在某些系统上,tput 使用的是termcap数据库;在其他系统上(大多数是新系统),它使用terminfo数据库。两个数据库的命令是不同的,所以为一个系统编写的tput命令可能在另一个系统上不起作用。
在一个系统中,将光标放在第 10 行第 20 列的命令如下:
tput cup 9 19
在另一个系统上,这是命令:
tput cm 9 19
这些命令将为TERM变量中指定的任何类型的终端产生正确的输出。(注:tput从 0 开始计数。)
然而,过多的终端类型,出于所有的意图和目的,已经减少到一个单一的,标准的类型。这个标准,ISO 6429(又名 ECMA-48,原名 ANSI X3.64 或 VT100),无处不在,不支持它的终端少之又少。因此,现在为单一终端类型编码是可行的。这种同质性的一个优点是必要的编码可以完全在 Shell 内完成。不需要外部命令。
电传打字机对帆布
将脚本的输出发送到终端屏幕有两种方法。第一种也是更传统的方法使用终端,就好像它是一台打印机或电传打字机一样(这就是屏幕或终端的缩写tty的由来)。在这种模式下,每打印一行,纸张(或屏幕图像)就会向上滚动。旧线条落到地板上(或从屏幕顶部消失)。它很简单,对于许多应用来说绰绰有余。
第二种方法将屏幕视为黑板或画布,并打印到其表面的特定点。它会擦除和套印先前写入的部分。它可以在屏幕上按列或特定位置打印文本。终端变成一个随机访问而不是串行的设备。
本章将屏幕视为画布或黑板。它为屏幕操作定义了许多变量和函数,并展示了一些使用它们的演示程序。
拉伸画布
要将屏幕用作画布,最重要的功能是能够将光标定位在屏幕上的任何给定位置。其顺序是ESC<ROW>;<COL>H。当转换为printf格式字符串时,它可以直接使用或在函数中使用:
cu_row_col=$'\e[%d;%dH'
printf "$cu_row_col" 5 10 ## Row 5, column 10
echo "Here I am!"
本章中的所有函数都是screen-funcs库的一部分,它是screen-vars文件的来源。[清单 14-1 给出了屏幕操作功能。
清单 14-1 。screen-funcs,屏幕操作函数库
. screen-vars
printat函数(清单 14-2 )将光标放在请求的位置,如果还有其他参数,它将打印出来。如果没有指定行和列,printat将光标移动到屏幕的左上角。
清单 14-2 。printat,将光标放在指定位置,打印可选字符串
printat() #@ USAGE: printat [row [column [string]]]
{
printf "${cu_row_col?}" ${1:-1} ${2:-1}
if [ $# -gt 2 ]
then
shift 2
printf "%s" "$*"
fi
}
命令序列导入器
像所有的转义序列一样,cu_row_col以ESC开始。这是命令序列介绍者(CSI) 。在screen-vars文件中定义([清单 14-3 )。
清单 14-3 。screen-vars,屏幕变量定义
ESC=$'\e'
CSI=$ESC
给画布涂底漆
在屏幕上绘图之前,通常必须清除它,并且不时地,屏幕的各个部分将需要被清除。这些变量包含清除屏幕或线条的基本序列([清单 14-4 )。
清单 14-4 。screen-vars,用于擦除全部或部分屏幕的变量定义
topleft=${CSI}H ## move cursor to top left corner of screen
cls=${CSI}J ## clear the screen
clear=$topleft$cls ## clear the screen and move to top left corner
clearEOL=${CSI}K ## clear from cursor to end of line
clearBOL=${CSI}1K ## clear from cursor to beginning of line
clearEOS=${CSI}0J ## clear from cursor to end of screen
clearBOS=${CSI}1J ## clear from cursor to beginning of screen
还有清除屏幕矩形区域的功能,这将在本章后面介绍。
移动光标
除了将移动到绝对位置,光标还可以相对于其当前位置移动。前四个序列与光标键生成的序列相同,它们接受移动多行或多列的参数。接下来的两个按钮打开和关闭光标。以下两个变量分别保存光标位置并将其移回保存的位置。
最后两个移动到下一行或上一行,与前一打印行的开头在同一列。删除了printf说明符、%s,因为它会消耗将要打印的参数(清单 14-5 )。
清单 14-5 。screen-vars,用于移动光标的变量定义
## cursor movement strings
cu_up=${CSI}%sA
cu_down=${CSI}%sB
cu_right=${CSI}%sC
cu_left=${CSI}%sD
## turn the cursor off and on
cu_hide=${CSI}?25l
cu_show=${CSI}?12l${CSI}?25h
## save the cursor position
cu_save=${CSI}s ## or ${ESC}7
## move cursor to saved position
cu_restore=${CSI}u ## or ${ESC}8
## move cursor to next/previous line in block
cu_NL=$cu_restore${cu_down/\%s/}$cu_save
cu_PL=$cu_restore${cu_up/\%s/}$cu_save
光标移动的格式字符串使用%s说明符而不是%d,即使任何参数都是数字。这是因为当没有参数填充时,printf用零替换%d。如果发生这种情况,光标根本不会移动。对于%s,当没有参数时,它们移动一列或一行,因为%s被一个空字符串代替。
清单 14-6 中的脚本让这些变量和printat函数开始工作。
清单 14-6 。screen-demo1,使printat工作的脚本
. screen-funcs ## source the screen-funcs library
printf "$clear$cu_hide" ## Clear the screen and hide the cursor
printat 10 10 "${cu_save}XX" ## move, save position, and print XX
sleep 1 ## ZZZZZZZZ
printat 20 20 "20/20" ## move and print
sleep 1 ## ZZZZZZZZ
printf "$cu_restore$cu_down${cu_save}YY" ## restore pos., move, print, save pos.
sleep 1 ## ZZZZZZZZ
printf "$cu_restore$cu_down${cu_save}ZZ" 4 ## restore pos., move, print, save pos.
sleep 1 ## ZZZZZZZZ
printat 1 1 "$cu_show" ## move to top left and show cursor
作为一种变化,尝试将第一个printat命令的坐标改为其他值,比如 5 和 40。
更改呈现模式和颜色
字符可以以粗体、下划线或反转模式打印,也可以在支持它们的终端上以各种颜色打印。(还有剩下不要的吗?)这些属性都用形式为ESC ATTRm的序列修改,其中ATTR是属性或颜色的编号([清单 14-7 )。可以通过用分号分隔来指定多个属性。
颜色用整数 0 到 7 指定,9 将重置为默认值。前景色以 3 为前缀,背景色以 4 为前缀。属性也由 0 到 7 指定,但没有前缀。虽然定义了八个属性,但只有三个得到广泛支持:1(粗体)、4(下划线)和 7(反转)。这些属性可以分别用值 22、24 和 27 单独关闭。值为 0 会将所有属性和颜色重置为默认值。
清单 14-7 。screen-vars,颜色和属性的变量定义
## colours
black=0
red=1
green=2
yellow=3
blue=4
magenta=5
cyan=6
white=7
fg=3 ## foreground prefix
bg=4 ## background prefix
## attributes
bold=1
underline=4
reverse=7
## set colors
set_bg="${CSI}4%dm" ## set background color
set_fg="${CSI}3%dm" ## set foreground color
set_fgbg="${CSI}3%d;4%dm" ## set foreground and background colors
如下一个演示脚本所示,颜色和属性可以在“tty”模式和“canvas”模式中使用(清单 14-8 )。
清单 14-8 。screen-demo2,颜色和属性模式
. screen-funcs
echo
for attr in "$underline" 0 "$reverse" "$bold" "$bold;$reverse"
do
printf "$set_attr" "$attr"
printf "$set_fg %s " "$red" RED
printf "$set_fg %s " "$green" GREEN
printf "$set_fg %s " "$blue" BLUE
printf "$set_fg %s " "$black" BLACK
printf "\em\n"
done
echo
在屏幕上放置一个文本块
put_block函数在当前光标位置一个接一个地打印其参数;put_block_at将光标移动到指定位置,移动参数以删除行和列,然后用剩余的参数调用put_block([清单 14-9 )。
cu_NL变量将光标移动到保存的位置,然后向下移动一行并保存该位置。
清单 14-9 。put_block``put_block_``at,在屏幕上的任何地方打印一块文本
put_block() #@ Print arguments in a block beginning at the current position
{
printf "$cu_save" ## save cursor location
printf "%s$cu_NL" "$@" ## restore cursor location, move line down, save cursor
}
put_block_at() #@ Print arguments in a block at the position in $1 and $2
{
printat "$1" "$2"
shift 2
put_block "$@"
}
清单 14-10 显示了screen-demo3的脚本,它在屏幕上以柱状格式显示数据块。
清单 14-10 。screen-demo3
. screenfuncs
printf "$cls"
put_block_at 3 12 First Second Third Fourth Fifth
put_block_at 2 50 January February March April May June July
screen-demo3的输出如下:
January
First February
Second March
Third April
Fourth May
Fifth June
July
当屏幕为空时,put_block和put_block_at功能工作正常。如果屏幕上已经有很多文本,输出可能会模糊不清。对于这些情况,有print_block_at和print_block函数清除块周围的矩形区域。
为了确定需要清除的宽度,put_block将其参数传递给_max_length函数,该函数循环遍历参数以找到最长的(清单 14-11 )。
清单 14-11 。_max_length,在_MAX_LENGTH中存储最长参数的长度
_max_length() #@ store length of longest argument in _MAX_LENGTH
{
local var
_MAX_LENGTH=${#1} ## initialize with length of first parameter
shift ## ...and remove first parameter
for var ## loop through remaining parameters
do
[ "${#var}" -gt "$_MAX_LENGTH" ] && _MAX_LENGTH=${#var}
done
}
print_block函数使用_max_length的结果作为printf ( 清单 14-12 )的宽度规格。文本前后打印空行,每行前后打印一个空格。print_block_at和put_block_at唯一的区别就是一个叫print_block,一个叫put_block。
清单 14-12 。print_block ,清除区域和打印块
print_block() #@ Print arguments in a block with space around them
{
local _MAX_LENGTH
_max_length "$@"
printf "$cu_save"
printf " %-${_MAX_LENGTH}s $cu_NL" " " "$@" " "
}
print_block_at() #@ Move to position, remove 2 parameters and call print_block
{
printat $1 $2
shift 2
print_block "$@"
}
用print_block或print_block打印的文本更可能是单个字符串,而不是单独的参数。要将字符串分割成足够短的单词或短语以适应给定的空间,使用wrap函数 ( 清单 14-13 )。这个函数将一个字符串拆分成具有命令行指定的最大宽度的行。
清单 14-13 。wrap,将字符串拆分成元素不超过最大长度的数组
wrap() #@ USAGE: wrap string length
{ #@ requires bash-3.1 or later
local words=$1 textwidth=$2 line= opts=$-
local len=0 templen=0
set -f
unset -v wrap
for word in $words
do
templen=$(( $len + 1 + ${#word} )) ## Test adding a word
if [ "$templen" -gt "$textwidth" ] ## Does adding a word exceed length?
then
wrap+=( "$line" ) ## Yes, store line in array
printf -v line "%s" "$word" ## begin new line
len=${#word}
else
len=$templen ## No, add word to line
printf -v line "%s" "${line:+"$line "}" "$word"
fi
done
wrap+=( "$line" )
case $opts in
*f*) ;;
*) set +f ;;
esac
}
在清单 14-14 中显示的例子使用了wrap和print_block_at。
清单 14-14 。screen-demo4,演示了wrap和print_block功能
clear
wrap "The quick brown fox jumps over the lazy dog" 15
x=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
printat 1 1
printf "%s\n" $x{,,,,,,,,,,} ## print 11 lines of 'x's
print_block_at 3 33 "${wrap[@]}"
printat 12 1
输出如下所示:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx The quick xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx brown fox jumps xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx over the lazy xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx dog xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
滚动文本
通过将数组与子串扩展相结合,文本可以在屏幕的任何区域滚动。因为整个区域可以用一个printf命令打印,所以滚动很快,尽管随着数组大小的增加,滚动会变慢。清单 14-15 中的演示将/usr/bin/中的文件名存储在数组list中;向上滚动列表;等待一秒钟;然后向下滚动。
每个循环,向上和向下,都包含一个注释掉的read -t "$delay"行。当取消注释时,它会减慢滚动速度。它使用bash-4.x小数延迟。如果您使用的是早期版本,请使用sleep来代替。大多数实现(当然是 GNU 和*BSD)都接受分数参数。
清单 14-15 。scroll-demo,上下滚动文本块
list=( /usr/bin/* ) ## try it with other directories or lists
rows=9 ## number of rows in scrolling area
delay=.01 ## delay between scroll advance
width=-33.33 ## width spec: (no more than) 33 chars, flush left
x=XXXXXXXXXXXXXXXXXXXXXXXXXX ## bar of 'X's
x=$x$x$x$x ## longer bar
clear ## clear the screen
printf "%50.50s\n" $x{,,,,,,,,,,,,,} ## print 14 lines of 'X's
n=0 ## start display with first element
## scroll upwards until reaching the bottom
while [ $(( n += 1 )) -lt $(( ${#list[@]} - $rows )) ]
do
printf "\e[3;1H"
printf "\e[7C %${width}s\n" "${list[@]:n:rows}"
# read -sn1 -t "$delay" && break
done
sleep 1
## scroll downwards until reaching the top
while [ $(( n -= 1 )) -ge 0 ]
do
printf "\e[3;1H"
printf "\e[7C %${width}s\n" "${list[@]:n:rows}"
# read -sn1 -t "$delay" && break
done
printf "\e15;1H" ## finish with cursor well below scrolling area
滚石杂志说
骰子在许多游戏中都有使用,如果您满足于只打印数字,那么编程起来很简单:
printf "%s\n" "$(( $RANDOM % 6 + 1 ))"
然而,一个令人满意的图形呈现可以用 shell 编程实现,而且非常容易。要打印骰子,将光标定位在屏幕上所需的位置,设置前景色和背景色,并打印数组中的元素([图 14-1 )。
图 14-1 。清单 14-16 包含了这些骰子的代码
可以用大约 25 行代码对六个骰子的阵列进行编程。每个骰子由 18 个变量串联而成。其中一些与那些在screen-funcs库中的内容相同,但是它们的名字在这里被缩短以保持行更短。以下是对编号为 5 的骰子的描述:
$b ## set bold attribute (optional)
$cs ## save cursor position
$p0 ## print blank row
$cr ## restore cursor to left side of die
$dn ## move down one line
$cs ## save cursor position
$p4 ## print row with two pips
$cr ## restore cursor to left side of die
$dn ## move down one line
$cs ## save cursor position
$p2 ## print row with one pip
$cr ## restore cursor to left side of die
$dn ## move down one line
$cs ## save cursor position
$p4 ## print row with two pips
$cr ## restore cursor to left side of die
$dn ## move down one line
$p0 ## print blank row
定义骰子后,清单 14-16 中的脚本清空屏幕,并在屏幕顶部附近打印两个随机骰子。
清单 14-16 。dice,定义六个骰子的阵列,并在屏幕上放置两个
pip=o ## character to use for the pips
p0=" " ## blank line
p1=" $pip " ## one pip at the left
p2=" $pip " ## one pipe in the middle of the line
p3=" $pip " ## one pip at the right
p4=" $pip $pip " ## two pips
p5=" $pip $pip $pip " ## three pips
cs=$'\e7' ## save cursor position
cr=$'\e8' ## restore cursor position
dn=$'\e[B' ## move down 1 line
b=$'\e[1m' ## set bold attribute
cu_put='\e[%d;%dH' ## format string to position cursor
fgbg='\e[3%d;4%dm' ## format string to set colors
dice=(
## dice with values 1 to 6 (array elements 0 to 5)
"$b$cs$p0$cr$dn$cs$p0$cr$dn$cs$p2$cr$dn$cs$p0$cr$dn$p0"
"$b$cs$p0$cr$dn$cs$p1$cr$dn$cs$p0$cr$dn$cs$p3$cr$dn$p0"
"$b$cs$p0$cr$dn$cs$p1$cr$dn$cs$p2$cr$dn$cs$p3$cr$dn$p0"
"$b$cs$p0$cr$dn$cs$p4$cr$dn$cs$p0$cr$dn$cs$p4$cr$dn$p0"
"$b$cs$p0$cr$dn$cs$p4$cr$dn$cs$p2$cr$dn$cs$p4$cr$dn$p0"
"$b$cs$p0$cr$dn$cs$p5$cr$dn$cs$p0$cr$dn$cs$p5$cr$dn$p0"
)
clear
printf "$cu_put" 2 5 ## position cursor
printf "$fgbg" 7 0 ## white on black
printf "%s\n" "${dice[RANDOM%6]}" ## print random die
printf "$cu_put" 2 20 ## position cursor
printf "$fgbg" 0 3 ## black on yellow
printf "%s\n" "${dice[RANDOM%6]}" ## print random die
摘要
不涉及传统的 ASCII 艺术,有许多方法可以在终端屏幕上画图。本章介绍了其中的一些,给出了可以用来创建更多的基础知识。
练习
- 编写一个函数
hbar,它接受两个整数参数,一个宽度和一个颜色,并打印一个具有该颜色和宽度的条形。编写第二个函数hbar_at,它接受四个参数:行、列、宽度和颜色;将光标移动到行和列;并将剩下的争论交给hbar。 - 编写一个函数
clear_area,它接受两个整数参数,即行和列,并清除多个行和列的矩形区域。
十五、入门级编程
与任何其他 POSIX shell 相比,bash更受青睐,这在很大程度上源于其增强交互式编程的扩展。对read内置命令的扩展选项(在第九章中有描述),结合history和readline库,添加了其他 shell 无法比拟的功能。
尽管它很丰富,但是对于 shell 来说,仍然没有简单的方法来处理诸如生成多个字符的功能键之类的键。为此,本章介绍了key-funcs函数库。本章的第二个主要部分描述了如何在 shell 脚本中使用鼠标,并提供了一个演示程序。
在这两个部分之间,我们将检查用户输入的有效性和历史库。大多数人只在命令行使用bash的历史库。我们将在脚本中使用它,本章将展示如何通过在编辑多字段记录的基本脚本中使用history命令来做到这一点。
单键输入
编写交互式脚本时,您可能希望只按一个键,而不要求用户按 Enter。可移植的方法是使用stty和dd:
stty -echo -icanon min 1
_KEY=$(dd count=1 bs=1 2>/dev/null)
stty echo icanon
每次需要按键时使用三个外部命令是多余的。当你需要使用一个可移植的方法时,你通常可以首先在脚本的开始调用stty,然后在最后调用另一个,通常在EXIT陷阱中:
trap 'stty echo icanon' EXIT
另一方面,Bash不需要调用任何外部命令。使用stty在开始时关闭回显并在退出前重新打开可能仍然是一个好主意。这将防止当脚本没有等待输入时字符出现在屏幕上。
函数库,按键功能
本节中的函数包括key-funcs库。它从两个变量定义开始,如清单 15-1 所示。
清单 15-1 。key-funcs,读取单个按键
ESC=$'\e'
CSI=$'\e'
要用bash获得一次击键,你可以使用清单 15-2 中的[函数。
清单 15-2 。_key,读取单键按下的功能
_key()
{
IFS= read -r -s -n1 -d '' "${1:-_KEY}"
}
首先,字段分隔符被设置为空字符串,这样read就不会忽略前导空格(这是一个有效的击键,所以你想要它);-r选项禁用反斜杠转义,-s关闭按键回显,-n1告诉bash只读取单个字符。
-d ''选项告诉read不要将换行符(或任何其他字符)视为输入的结尾;这允许在变量中存储一个新行。该代码指示read在收到第一个密钥(-n1)后停止,因此它不会一直读取。
最后一个参数使用${@:-_KEY}将选项或变量名添加到参数列表中。您可以在清单 15-3 中的函数中看到它的用法。(注意,如果你使用一个没有变量名的选项,输入将被存储在$REPLY。)
注意为了在早期版本的
bash或 Mac OS X 上工作,将变量名添加到read命令中,例如IFS= read –r –s –n1 –d'' _KEY "${1:-_KEY}"。如果没有,那么您必须查看$REPLY中的按键读数。
在一个简单的菜单中可以使用_key功能,如清单 15-3 所示。
清单 15-3 。simplemenu,响应单次按键的菜单
## the _key function should be defined here if it is not already
while :
do
printf "\n\n\t$bar\n"
printf "\t %d. %s\n" 1 "Do something" \
2 "Do something else" \
3 "Quit"
printf "\t%s\n" "$bar"
_key
case $_KEY in
1) printf "\n%s\n\n" Something ;;
2) printf "\n%s\n\n" "Something else" ;;
3) break ;;
*) printf "\a\n%s\n\n" "Invalid choice; try again"
continue
;;
esac
printf ">>> %s " "Press any key to continue"
_key
done
尽管_key本身是一个有用的函数,但它有其局限性(清单 15-4 )。它可以存储空格、换行符、控制代码或任何其他单个字符,但是它不处理返回多个字符的键:功能键、光标键和其他一些键。
这些特殊键返回 ESC (0 × 1B,保存在变量$ESC中),后跟一个或多个字符。字符的数量根据键(和终端仿真)的不同而不同,因此您不能要求特定数量的键。相反,您必须循环,直到读取到一个终止字符。这就是使用bash内置的read命令而不是外部的dd有所帮助的地方。
清单 15-4 。_keys,从功能或光标键中读取一系列字符
_keys() #@ Store all waiting keypresses in $_KEYS
{
_KEYS=
__KX=
## ESC_END is a list of characters that can end a key sequence
## Some terminal emulations may have others; adjust to taste
ESC_END=[a-zA-NP-Z~^\$@$ESC]
while :
do
IFS= read -rsn1 -d '' -t1 __KX
_KEYS=$_KEYS$__KX
case $__KX in
"" | $ESC_END ) break ;;
esac
done
}
while :循环使用参数-t1调用_key,参数【】告诉 read 在一秒钟后超时,变量的名称用于存储击键。循环继续,直到$ESC_END中的一个键被按下或者read超时,剩下$__KX为空。
超时本身是检测退出键的一种部分令人满意的方法。在这种情况下,dd比read工作得更好,因为它可以设置为以十分之一秒的增量超时。
要测试这些函数,使用_key来获取单个字符;如果那个字符是ESC,调用_keys 来读取序列的剩余部分(如果有的话)。下面的代码片段假设已经定义了_key和_keys,并通过hexdump - C 管道显示每个击键的内容:
while :
do
_key
case $_KEY in
$ESC) _keys
_KEY=$ESC$_KEYS
;;
esac
printf "%s" "$_KEY" | hexdump -C | {
read a b
printf " %s\n" "$b"
}
case "$_KEY" in q) break ;; esac
done
不同于在任何地方都能工作的输出序列,由各种终端仿真器产生的按键序列之间没有同质性。以下是在rxvt终端窗口中,按 F1、F12、上箭头、Home 和 q 退出的运行示例:
1b 5b 31 31 7e |.11~|
1b 5b 32 34 7e |.[24~|
1b 5b 41 |.[A|
1b 5b 35 7e |.[5~|
71 |q|
以下是xterm窗口中的相同击键:
1b 4f 50 |.OP|
1b 5b 32 34 7e |.[24~|
1b 5b 41 |.[A|
1b 5b 48 |.[H|
71 |q|
最后,这是由 Linux 虚拟控制台生成的:
1b 5b 5b 41 |.[[A|
1b 5b 32 34 7e |.[24~|
1b 5b 41 |.[A|
1b 5b 31 7e |.[1~|
71 |q|
所有被测试的终端都属于这三组中的一组,至少对于未修改的密钥是这样。
存储在$_KEY中的代码可以直接解释,也可以在单独的函数中解释。最好将解释保存在一个函数中,该函数可以被替换以用于不同的终端类型。例如,如果您使用 Wyse60 终端,source wy60-keys函数将设置替换键。
[清单 15-5 显示了一个函数_esc2key,它适用于 Linux 系统中的各种终端,也适用于 Windows 系统中的putty。它将字符序列转换为描述按键的字符串,例如 UP、DOWN、F1 等:
清单 15-5 。_esc2key,将字符串翻译成键名
_esc2key()
{
case $1 in
## Cursor keys
"$CSI"A | ${CSI}OA ) _ESC2KEY=UP ;;
"$CSI"B | ${CSI}0B ) _ESC2KEY=DOWN ;;
"$CSI"C | ${CSI}OC ) _ESC2KEY=RIGHT ;;
"$CSI"D | ${CSI}OD ) _ESC2KEY=LEFT ;;
## Function keys (unshifted)
"$CSI"11~ | "$CSI["A | ${ESC}OP ) _ESC2KEY=F1 ;;
"$CSI"12~ | "$CSI["B | ${ESC}OQ ) _ESC2KEY=F2 ;;
"$CSI"13~ | "$CSI["C | ${ESC}OR ) _ESC2KEY=F3 ;;
"$CSI"14~ | "$CSI["D | ${ESC}OS ) _ESC2KEY=F4 ;;
"$CSI"15~ | "$CSI["E ) _ESC2KEY=F5 ;;
"$CSI"17~ | "$CSI["F ) _ESC2KEY=F6 ;;
"$CSI"18~ ) _ESC2KEY=F7 ;;
"$CSI"19~ ) _ESC2KEY=F8 ;;
"$CSI"20~ ) _ESC2KEY=F9 ;;
"$CSI"21~ ) _ESC2KEY=F10 ;;
"$CSI"23~ ) _ESC2KEY=F11 ;;
"$CSI"24~ ) _ESC2KEY=F12 ;;
## Insert, Delete, Home, End, Page Up, Page Down
"$CSI"2~ ) _ESC2KEY=INS ;;
"$CSI"3~ ) _ESC2KEY=DEL ;;
"$CSI"[17]~ | "$CSI"H ) _ESC2KEY=HOME ;;
"$CSI"[28]~ | "$CSI"F ) _ESC2KEY=END ;;
"$CSI"5~ ) _ESC2KEY=PGUP ;;
"$CSI"6~ ) _ESC2KEY=PGDN ;;
## Everything else; add other keys before this line
*) _ESC2KEY=UNKNOWN ;;
esac
[ -n "$2" ] && eval "$2=\$_ESC2KEY"
}
您可以将_key和_esc2key函数包装成另一个函数,称为get_key ( 清单 15-6 ),它返回按下的单个字符,或者在多字符键的情况下,返回键的名称。
清单 15-6 。获取一个密钥,如果需要的话,将其翻译成一个密钥名
get_key()
{
_key
case $_KEY in
"$ESC") _keys
_esc2key "$ESC$_KEYS" _KEY
;;
esac
}
在bash-4.x中,可以使用更简单的函数来读取击键。清单 15-7 中的get_key函数利用了read的-t选项接受小数时间的能力。它读取第一个字符,然后等待万分之一秒的时间来读取另一个字符。如果按下了多字符键,在这段时间内将会有一个字符被读取。否则,在按下另一个键之前,它将通过剩余的read语句。
清单 15-7 。get_key,读取一个键,如果不止一个字符,则将其翻译成一个键名
get_key() #@ USAGE: get_key var
{
local _v_ _w_ _x_ _y_ _z_ delay=${delay:-.0001}
IFS= read -d '' -rsn1 _v_
read -sn1 -t "$delay" _w_
read -sn1 -t "$delay" _x_
read -sn1 -t "$delay" _y_
read -sn1 -t "$delay" _z_
case $_v_ in
$'\e') _esc2key "$_v_$_w_$_x_$_y_$_z_"
printf -v ${1:?} $_ESC2KEY
;;
*) printf -v ${1:?} "%s" "$_v_$_w_$_x_$_y_$_z_" ;;
esac
}
每当您想在脚本中使用光标或功能键,或者任何单键输入,您可以调用key-funcs来捕获按键。清单 15-8 是使用该库的简单演示。
清单 15-8 。keycapture,读取并显示击键,直到 Q 被按下
. key-funcs ## source the library
while : ## infinite loop
do
get_key key
sa "$key" ## the sa command is from previous chapters
case $key in q|Q) break;; esac
done
清单 15-9 中的脚本在屏幕上打印一个文本块。可以用光标键在屏幕上移动,用功能键改变颜色。奇数功能键改变前景色;偶数键改变背景。
清单 15-9 。key-demo、捕捉功能和光标键改变颜色并在屏幕上移动文本块
trap '' 2
trap 'stty sane; printf "${CSI}?12l${CSI}?25h\e[0m\n\n"' EXIT
stty -echo ## Turn off echoing of user keystrokes
. key-funcs ## Source key functions
clear ## Clear the screen
bar=====================================
## Initial position for text block
row=$(( (${LINES:-24} - 10) / 2 ))
col=$(( (${COLUMNS:-80} - ${#bar}) / 2 ))
## Initial colours
fg="${CSI}33m"
bg="${CSI}44m"
## Turn off cursor
printf "%s" "${CSI}?25l"
## Loop until user presses "q"
while :
do
printf "\e[1m\e[%d;%dH" "$row" "$col"
printf "\e7 %-${#bar}.${#bar}s ${CSI}0m \e8\e[1B" "${CSI}0m"
printf "\e7 $fg$bg%-${#bar}.${#bar}s${CSI}0m \e8\e[1B" "$bar" \
"" " Move text with cursor keys" \
"" " Change colors with function keys" \
"" " Press 'q' to quit" \
"" "$bar"
printf "\e7%-${#bar}.${#bar}s " "${CSI}0m"
get_key k
case $k in
UP) row=$(( $row - 1 )) ;;
DOWN) row=$(( $row + 1 )) ;;
LEFT) col=$(( $col - 1 )) ;;
RIGHT) col=$(( $col + 1 )) ;;
F1) fg="${CSI}30m" ;;
F2) bg="${CSI}47m" ;;
F3) fg="${CSI}31m" ;;
F4) bg="${CSI}46m" ;;
F5) fg="${CSI}32m" ;;
F6) bg="${CSI}45m" ;;
F7) fg="${CSI}33m" ;;
F8) bg="${CSI}44m" ;;
F9) fg="${CSI}35m" ;;
F10) bg="${CSI}43m" ;;
F11) fg="${CSI}34m" ;;
F12) bg="${CSI}42m" ;;
q|Q) break ;;
esac
colmax=$(( ${COLUMNS:-80} - ${#bar} - 4 ))
rowmax=$(( ${LINES:-24} - 10 ))
[ $col -lt 1 ] && col=1
[ $col -gt $colmax ] && col=$colmax
[ $row -lt 1 ] && row=1
[ $row -gt $rowmax ] && row=$rowmax
done
脚本中的历史记录
在第六章和第十二章的readline函数中,history -s用于将默认值放入历史列表中。在这些例子中,只存储了一个值,但是可以在历史中存储多个值,甚至可以使用整个文件。在添加到历史记录之前,您应该(在大多数情况下)清除它:
history -c
通过使用多个history -s命令,您可以存储多个值:
history -s Genesis
history -s Exodus
使用-r选项,您可以将整个文件读入历史。这个片段将圣经中前五本书的名字放入一个文件中,并读入历史:
cut -d: -f1 "$kjv" | uniq | head -5 > pentateuch
history -r pentateuch
第六章和第十二章中的readline函数在bash版本小于 4 时使用history,但在版本 4(或更高版本)时使用read的-i选项。有时使用history比使用-i更合适,即使后者可用。一个恰当的例子是,新输入可能与默认输入非常不同,但也有可能并非如此。
为了使历史可用,您必须使用带有read的-e选项。这也让您可以访问在您的.inputrc文件中定义的其他键绑定。
健全检查
健全性检查是测试输入的正确类型和合理值。如果用户输入简作为她的年龄,这显然是错误的:数据是错误的类型。如果她输入 666 ,这是正确的类型,但几乎肯定是不正确的值。使用valint脚本(参见第三章)或函数(参见第六章)很容易检测出不正确的类型。您可以使用第六章中的rangecheck功能来检查合理的值。
有时错误更成问题,甚至是恶意的。假设一个脚本要求变量名,然后使用eval给它赋值:
read -ep "Enter variable name: " var
read -ep "Enter value: " val
eval "$var=\$val"
现在,假设条目是这样的:
Enter variable name: rm -rf *;name
Enter value: whatever
eval将执行的命令如下:
rm -rf *;name=whatever
噗!你所有的文件和子目录都从当前目录中消失了。通过使用第七章中的validname函数检查var的值,可以防止这种情况的发生:
validname "$var" && eval "$var=\$val" || echo Bad variable name >&2
编辑数据库时,检查没有无效字符是一个重要的步骤。例如,在编辑/etc/passwd(或创建它的表格)时,您必须确保任何字段中没有冒号。图 15-1 给这个讨论增加了一些幽默。
图 15-1 。漫画由兰道尔·门罗在http://xkcd.com提供
表单条目
清单 15-10 中的脚本演示了如何用菜单和历史来处理用户输入。它使用key-funcs库来获取用户的选择并编辑密码字段。它有一个硬编码的记录,并且不读取/etc/passwd文件。它检查条目中的冒号,如果发现冒号,则打印一条错误消息。
记录从 here 文档读入一个数组。一个单独的printf语句打印菜单,使用带有七个空格 的格式字符串和整个数组作为它的参数。
清单 15-10 。password,简单的记录编辑脚本
record=root:x:0:0:root:/root:/bin/bash ## record to edit
fieldnames=( User Password UID
GID Name Home Shell )
. key-funcs ## load the key functions
IFS=: read -a user <<EOF ## read record into array
$record
EOF
z=0
clear
while : ## loop until user presses 0 or q
do
printf "\e[H\n
0. Quit
1. User: %s\e[K
2\. Password: %s\e[K
3. UID: %s\e[K
4. GID: %s\e[K
5. Name: %s\e[K
6. Home: %s\e[K
7. Shell: %s\e[K
Select field (1-7): \e[0J" "${user[@]}" ## print menu and prompt
get_key field ## get user input
printf "\n\n" ## print a blank line
case $field in
0|q|Q) break ;; ## quit
[1-7]) ;; ## menu item selected; fall through
*) continue;;
esac
history -c ## clear history
history -s "${user[field-1]}" ## insert current value in history
printf ' Press UP to edit "%s"\n' "${user[field-1]}" ## tell user what's there
read -ep " ${fieldnames[field-1]}: " val ## get user entry
case $val in
*:*) echo " Field may not contain a colon (press ENTER)" >&2 ## ERROR
get_key; continue
;;
"") continue ;;
*) user[field-1]=$val ;;
esac
done
阅读鼠标
在 Linux console_codes 1 手册页上,有一个标注为“鼠标跟踪 的部分有意思!上面写着:“鼠标跟踪工具旨在返回与xterm兼容的鼠标状态报告。”这是否意味着鼠标可以在 shell 脚本中使用?
根据手册页,鼠标跟踪有两种模式 : X10 兼容模式,它在按钮按下时发送一个转义序列;正常跟踪模式,它在按钮按下和释放时都发送一个转义序列。两种模式都会发送修饰键信息。
为了测试这一点,首先在终端窗口输入printf "\e[?9h"。这是设置“X10 鼠标报告(默认关闭):将报告模式设置为 1(或重置为 0)”的转义序列。如果你按下鼠标键,电脑会发出哔哔声,并在屏幕上打印“FB”。在屏幕上的不同点重复点击鼠标会产生更多的蜂鸣声和“&% -( 5\. =2 H7 T= ]C fG rJ }M”
鼠标点击发送六个字符:ESC、``、M、 b 、 x 、 y 。前三个字符是所有鼠标事件共有的,后三个包含按下的按钮,最后三个是鼠标的x和y位置。为了确认这一点,将输入保存在一个变量中,并通过管道传输到hexdump:
$ printf "\e[?9h"
$ read x
^[[M!MO ## press mouse button and enter
$ printf "$x" | hexdump -C
00000000 1b 5b 4d 21 4d 4f |.[M!MO|
00000006
前三个如期出现,但最后三个是什么?根据手册页,按钮字符的低两位表示按下了哪个按钮;高位标识活动修改器。x和y坐标是 ASCII 值,加上 32 以使它们脱离控制字符的范围。!是 1,"是 2,以此类推。
这给了我们一个鼠标按钮的1,这意味着按钮 2,因为0到2分别是按钮 1、2 和 3,而4是释放。x和y坐标为 45(O×4d = 77;77–32 = 45)和 47。
令人惊讶的是,自从在 Linux 手册页中浏览了这些关于鼠标跟踪的信息后,发现这些转义码并不适用于所有的 Linux 控制台。他们在 Linux 和 FreeBSD 上的xterm、rxvt和gnome-terminal中工作。它们也可以在 FreeBSD 和 NetBSD 上使用,通过 Linux rxvt终端窗口的ssh。他们不在 KDE 窗口中工作。
您现在知道鼠标报告是可行的(在大多数xterm窗口中),您可以通过鼠标点击标准输入来获得信息。这就留下了两个问题:如何将信息读入变量(无需按回车键),如何在 shell 脚本中解码按钮和x、y信息?
对于bash,使用read命令的-n选项 和一个参数来指定字符数。要阅读鼠标,需要六个字符:
read -n6 x
对于真实的脚本来说,这两种方法都不够(不是所有的输入都是鼠标点击,您可能希望获得单次击键),但是它们足以演示这个概念。
下一步是解码输入。出于演示的目的,您可以假设这六个字符确实代表鼠标点击,前三个字符是ESC、[和M。这里我们只对最后三个感兴趣,所以我们使用 POSIX 参数扩展将它们提取到三个独立的变量中:
m1=${x#???} ## Remove the first 3 characters
m2=${x#????} ## Remove the first 4 characters
m3=${x#?????} ## Remove the first 5 characters
然后将每个变量的第一个字符转换成它的 ASCII 值。这使用了 POSIX printf扩展:“如果前导字符是单引号或双引号,则该值应该是单引号或双引号后面字符的基础代码集中的数值。” [2
printf -v mb "%d" "'$m1"
printf -v mx "%d" "'$m2"
printf -v my "%d" "'$m3"
最后,解释 ASCII 值。对于鼠标按钮,做一个按位AND 3。对于x和y坐标,减去 32:
## Values > 127 are signed, so fix if less than 0
[ $mx -lt 0 ] && mx=$(( 255 + $mx ))
[ $my -lt 0 ] && my=$(( 255 + $my ))
BUTTON=$(( ($mb & 3) + 1 ))
MOUSEX=$(( $mx - 32 ))
MOUSEY=$(( $my - 32 ))
综上所述,清单 15-11 中的脚本会在你按下鼠标按钮时打印出鼠标的坐标。
最上面一排有两个敏感区域。单击左键可以在只报告按键和报告释放之间切换鼠标报告模式。单击右边的按钮退出脚本。
清单 15-11 。mouse-demo,读取鼠标点击的例子
ESC=$'\e'
but_row=1
mv=9 ## mv=1000 for press and release reporting; mv=9 for press only
_STTY=$(stty -g) ## Save current terminal setup
stty -echo -icanon ## Turn off line buffering
printf "${ESC}[?${mv}h " ## Turn on mouse reporting
printf "${ESC}[?25l" ## Turn off cursor
printat() #@ USAGE: printat ROW COLUMN
{
printf "${ESC}[${1};${2}H"
}
print_buttons()
{
num_but=$#
gutter=2
gutters=$(( $num_but + 1 ))
but_width=$(( ($COLUMNS - $gutters) / $num_but ))
n=0
for but_str
do
col=$(( $gutter + $n * ($but_width + $gutter) ))
printat $but_row $col
printf "${ESC}[7m%${but_width}s" " "
printat $but_row $(( $col + ($but_width - ${#but_str}) / 2 ))
printf "%.${but_width}s${ESC}[0m" "$but_str"
n=$(( $n + 1 ))
done
}
clear
while :
do
[ $mv -eq 9 ] && mv_str="Click to Show Press & Release" ||
mv_str="Click to Show Press Only"
print_buttons "$mv_str" "Exit"
read -n6 x
m1=${x#???} ## Remove the first 3 characters
m2=${x#????} ## Remove the first 4 characters
m3=${x#?????} ## Remove the first 5 characters
## Convert to characters to decimal values
printf -v mb "%d" "'$m1"
printf -v mx "%d" "'$m2"
printf -v my "%d" "'$m3"
## Values > 127 are signed
[ $mx -lt 0 ] && MOUSEX=$(( 223 + $mx )) || MOUSEX=$(( $mx - 32 ))
[ $my -lt 0 ] && MOUSEY=$(( 223 + $my )) || MOUSEY=$(( $my - 32 ))
## Button pressed is in first 2 bytes; use bitwise AND
BUTTON=$(( ($mb & 3) + 1 ))
case $MOUSEY in
$but_row) ## Calculate which on-screen button has been pressed
button=$(( ($MOUSEX - $gutter) / $but_width + 1 ))
case $button in
1) printf "${ESC}[?${mv}l"
[ $mv -eq 9 ] && mv=1000 || mv=9
printf "${ESC}[?${mv}h"
[ $mv -eq 1000 ] && x=$(dd bs=1 count=6 2>/dev/null)
;;
2) break ;;
esac
;;
*) printat $MOUSEY $MOUSEX
printf "X=%d Y=%d [%d] " $MOUSEX $MOUSEY $BUTTON
;;
esac
done
printf "${ESC}?${mv}l" ## Turn off mouse reporting
stty "$_STTY" ## Restore terminal settings
printf "${ESC}[?12l${ESC}[?25h" ## Turn cursor back on
printf "\n${ESC}[0J\n" ## Clear from cursor to bottom of screen,
摘要
拥有丰富的交互式编程选项。在本章中,你学会了如何利用它来读取任何击键,包括功能键和其他返回多个字符的键。
练习
- 使用
key-funcs库,编写一个使用功能键进行选择的菜单脚本。 - 重写
key-funcs库以包含鼠标操作,并将该功能并入mouse-demo脚本。 password脚本对无效条目进行最少的检查。你会添加什么检查?你会怎么编码?
[1
2
十六、附录 A:Shell 变量
该列表摘自bash手册页,并经过编辑成为一个独立的文档。以下变量由bash设定。
尝试
扩展到用于调用这个bash实例的完整的文件名。
巴什 PID
扩展到当前bash进程的进程 ID。这在某些情况下不同于$$,比如不需要bash重新初始化的子 Shell。
BASH _ 别名
一个关联数组变量,其成员对应于由别名内置维护的内部别名列表。添加到此数组的元素出现在别名列表中;取消设置数组元素会导致别名从别名列表中删除。
ARGC 巴什
一个数组变量,其值为当前bash执行调用栈中每一帧的参数个数。当前子程序(shell 函数或用.或 source 执行的脚本)的参数数量在堆栈的顶部。当一个子程序被执行时,传递的参数数被推送到BASH_ARGC。shell 仅在扩展调试模式下设置BASH_ARGC(参见bash手册页中对shopt内置的extdebug选项的描述)。
BASH_ARGV 函数
包含当前bash 执行调用栈中所有参数的数组变量。最后一个子例程调用的最后一个参数在栈顶;初始调用的第一个参数在底部。当子程序被执行时,所提供的参数被推送到BASH_ARGV上。shell 仅在扩展调试模式下设置BASH_ARGV(参见bash手册页中对shopt内置的extdebug选项的描述)。
CMDS 巴什
一个关联数组变量,其成员对应于哈希内置维护的命令内部哈希表。添加到此数组的元素出现在哈希表中;取消设置数组元素会导致命令从哈希表中删除。
BASH _ 命令
当前正在执行或即将执行的命令,除非 Shell 由于陷阱而正在执行命令,在这种情况下,它是陷阱发生时正在执行的命令。
BASH _ 执行 _ 字符串
-c调用选项的命令参数。
BASH_LINENO
一个数组变量,它的成员是对应于FUNCNAME每个成员的源文件中的行号。${BASH_LINENO[$i]}是源文件中调用${FUNCNAME[$i]}的行号(如果在另一个 shell 函数中引用,则为${BASH_LINENO[$i-1]})。对应的源文件名是${BASH_SOURCE[$i]}。使用LINENO获取当前行号。
BASH _ 重赛
一个数组变量,其成员由=~二元运算符分配给[[条件命令。索引为0的元素是字符串中匹配整个正则表达式的部分。索引为n的元素是字符串中匹配第 n 个带括号的子表达式的部分。此变量是只读的。
BASH_SOURCE
一个数组变量,其成员是对应于FUNCNAME数组变量中元素的源文件名。
BASH_SUBSHELL
每产生一个子 Shell 或子 Shell 环境就增加 1。初始值为 0。
BASH _ versi info
一个只读数组变量,其成员保存了bash实例的版本信息。分配给数组成员的值如下:
BASH_VERSINFO[0]:主要版本号(发布)BASH_VERSINFO[1]:次要版本号(版本)BASH_VERSINFO[2]:补丁级别BASH_VERSINFO[3]:构建版本BASH_VERSINFO[4]:发布状态(如beta1)BASH_VERSINFO[5]:MACHTYPE的值
BASH_VERSION
扩展为描述这个bash实例版本的字符串。
COMP_CWORD
包含当前光标位置的单词的${COMP_WORDS}索引。该变量仅在由可编程完成工具调用的 shell 函数中可用(参见bash手册页中的“可编程完成”)。
复合键
用于调用当前完成功能的键(或键序列的最后一个键)。
COMP_LINE
当前命令行。此变量仅在可编程完成功能调用的 Shell 函数和外部命令中可用(参见bash手册页中的“可编程完成”)。
红利点数
当前光标位置相对于当前命令开头的索引。如果当前光标位置在当前命令的末尾,该变量的值等于${#COMP_LINE}。该变量仅在可编程完成工具调用的 shell 函数和外部命令中可用(参见bash手册页中的“可编程完成”)。
组件类型
设置一个整数值,对应于导致调用补全函数的补全尝试类型:TAB用于正常补全,?用于在连续制表符后列出补全,!用于列出部分单词补全的备选项,@用于列出单词未被修改的补全,或者%用于菜单补全。该变量仅在可编程完成工具调用的 Shell 函数和外部命令中可用(参见bash手册页中的“可编程完成”)。
COMP_WORDBREAKS
执行单词补全时,readline库将其视为单词分隔符的字符集。如果COMP_ WORDBREAKS 未置位,它将失去其特殊属性,即使它随后被复位。
比较词
由当前命令行中的单个单词组成的数组变量(参见bash手册页中的“数组”)。如前所述,使用 COMP_WORDBREAKS将该行拆分成单词。该变量仅在可编程完成工具调用的 Shell 函数中可用(参见bash手册页中的“可编程完成”)。
DIRSTACK 先生
包含目录堆栈当前内容的数组变量(参见bash手册页中的“数组”)。目录按照dirs内置程序显示的顺序出现在堆栈中。分配给这个数组变量的成员可以用来修改栈中已经存在的目录,但是pushd和popd内置必须用来添加和删除目录。给这个变量赋值不会改变当前目录。如果DIRSTACK未被设置,它将失去其特殊属性,即使它随后被重置。
-你好
扩展到当前用户的有效用户 ID,在 shell 启动时初始化。此变量是只读的。
FUNCNAME
一个数组变量,包含当前执行调用堆栈中所有 shell 函数的名称。索引为 0 的元素是任何当前正在执行的 shell 函数的名称。最底层的元素是main。该变量仅在 shell 函数执行时存在。对FUNCNAME的赋值无效,并返回一个错误状态。如果FUNCNAME未被设置,它将失去其特殊属性,即使它随后被重置。
组
一个数组变量,包含当前用户所属的组的列表。对GROUPS的赋值无效,并返回一个错误状态。如果GROUPS未置位,它将失去其特殊属性,即使它随后被复位。
HISTCMD
当前命令的历史编号,或历史列表中的索引。如果HISTCMD未置位,它将失去其特殊属性,即使它随后被复位。
主机名
自动设置为当前主机的名称。
主机类型
自动设置为一个字符串,该字符串唯一地描述了执行bash的机器的类型。默认值取决于系统。
直线
每次引用这个参数时,shell 都会替换一个十进制数,表示脚本或函数中当前的顺序行号(从 1 开始)。当不在脚本或函数中时,不能保证被替换的值是有意义的。如果LINENO未置位,它将失去其特殊属性,即使它随后被复位。
公式编辑器
以标准的 GNU cpu-company-system 格式,自动设置为一个字符串,该字符串完整描述了bash正在其上执行的系统类型。默认值取决于系统。
老普德
由cd命令设置的先前工作的目录。
OPTARG(选项)
由getopts builtin 命令处理的最后一个选项参数的值(请参见bash手册页中的“Shell Builtin 命令”)。
太棒了
由getopts内置命令处理的下一个参数的索引(参见bash手册页中的“Shell 内置命令”)。
购买类型
自动设置为描述执行bash的操作系统的字符串。默认值取决于系统。
管道状态
一个数组变量(参见bash手册页中的“数组”),包含最近执行的前台管道中进程的退出状态值列表(可能只包含一个命令)。
PPID 是
shell 的父进程的进程 ID。此变量是只读的。
(美国)公共工程处(Public Works Department)
由cd命令设置的当前工作目录。
随意
每次引用该参数时,都会产生一个 0 到 32767 之间的随机整数。随机数序列可以通过给RANDOM赋值来初始化。如果RANDOM未置位,它将失去其特殊属性,即使它随后被复位。
回答
当没有提供参数时,设置由read内置命令读取的输入行。
秒
每次这个参数被引用时,返回自 shell 调用以来的秒数。如果给SECONDS赋值,后续引用返回的值是赋值后的秒数加上赋值后的值。如果SECONDS未置位,它将失去其特殊属性,即使它随后被复位。
甲壳动物
用冒号分隔的启用的 shell 选项列表。列表中的每个单词都是set内置命令的-o选项的有效参数(参见bash手册页中的“Shell 内置命令”)。出现在SHELLOPTS中的选项是由set -o报告的选项。如果bash启动时该变量在环境中,则在读取任何启动文件之前,列表中的每个 shell 选项都将被启用。此变量是只读的。
嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘嘘
每次bash的实例启动时增加 1。
用户界面设计(User Interface Design 的缩写)
扩展到当前用户的用户 ID,在 shell 启动时初始化。此变量是只读的。
Shell 使用以下变量。在某些情况下,bash给一个变量赋一个默认值;这些案例将在以下章节中说明。
BASH_ENV
如果在 bash执行 shell 脚本时设置了该参数,其值将被解释为包含初始化 shell 命令的文件名,如~/.bashrc所示。在被解释为文件名之前,BASH_ENV的值经过参数扩展、命令替换和算术扩展。PATH不用于搜索结果文件名。
CDPATH(路径)
cd命令的搜索路径。这个是一个用冒号分隔的目录列表,shell 在其中查找由cd命令指定的目标目录。一个样本值是.:~:/usr。
列
由select内置命令用来确定打印选择列表时的终端宽度。这在收到SIGWINCH时自动设置。
理解
一个数组变量,bash 从中读取由可编程完成工具调用的 shell 函数生成的可能完成(参见bash手册页中的“可编程完成”)。
编辑器
当 shell 以值t开始时,如果bash在环境中找到这个变量,它假定 shell 正在运行在一个emacs shell 缓冲区中,并禁用行编辑。
mcedit
默认编辑器为 fc内置命令。
别理他
执行文件名补全时要忽略的以冒号分隔的后缀列表(请参见bash手册页中的READLINE)。后缀与FIGNORE中的条目之一匹配的文件名从匹配文件名列表中排除。样本值为.o:~。
全球忽略
用冒号分隔的模式列表定义了路径名扩展要忽略的文件名集合。如果与路径名扩展模式匹配的文件名也与GLOBIGNORE中的模式之一匹配,它将从匹配列表中删除。
列举控件
用冒号分隔的值列表,控制命令如何保存在历史列表中。如果值列表包含ignorespace,以空格字符开头的行不会保存在历史列表中。值ignoredups导致匹配先前历史条目的行不被保存。值ignoreboth是ignorespace和ignoredups的简写。值erasedups导致在保存当前行之前,匹配当前行的所有先前行从历史列表中移除。不在先前列表中的任何值都将被忽略。如果HISTCONTROL未设置或不包含有效值,shell 解析器读取的所有行都保存在历史列表中,取决于HISTIGNORE的值。不测试多行复合命令的第二行和后续行,并将其添加到历史记录中,与HISTCONTROL的值无关。
HISTFILE(历史文件)
保存命令历史的文件名(参见bash手册页中的HISTORY)。默认值为~/.bash_history。如果未设置,当交互式 shell 退出时,不保存命令历史记录。
历史文件大小
历史文件中包含的最大行数。当此变量被赋值时,历史文件将被截断,如有必要,删除最早的条目,使其包含的行数不超过该值。默认值为 500。当交互式 shell 退出时,历史文件在写入后也会被截断到这个大小。
HISTIGNORE
冒号分隔的模式列表用于决定哪些命令行应该保存在历史列表中。每个模式被锚定在该行的开头,并且必须匹配完整的行(没有附加隐含的*)。在应用了HISTCONTROL指定的检查后,对照生产线测试每个图形。除了正常的 shell 模式匹配字符外,&匹配以前的历史行。&可以用反斜杠转义;在尝试匹配之前,会删除反斜杠。不测试多行复合命令的第二行和后续行,不管HISTIGNORE的值是多少,都将其添加到历史记录中。
组份大小
命令历史中要记住的命令数量(参见bash手册页中的HISTORY)。默认值为 500。
历史时间格式
如果这个变量被设置并且不为空,它的值被用作格式字符串,供strftime(3)打印与history内置显示的每个历史条目相关的时间戳。如果设置了此变量,时间戳将被写入历史文件,因此可以跨 shell 会话保存时间戳。这使用历史注释字符来区分时间戳和其他历史行。
家
当前用户的主目录;cd内置命令的默认参数。执行波浪号展开时,也会使用此变量的值。
档案空间
包含一个文件名,其格式与 shell 需要完成主机名时应该读取的格式相同。在 shell 运行时,可能的主机名完成列表可能会改变;值更改后,下次尝试完成主机名时,bash会将新文件的内容添加到现有列表中。如果HOSTFILE被设置但没有值,bash试图读取/etc/hosts以获得可能的主机名完成列表。当HOSTFILE未置位时,主机名列表被清除。
可安装文件系统
内部字段分隔符,用于扩展后的单词拆分,以及使用read内置命令将行拆分成单词。默认值为''''。
无知的
控制一个交互 Shell 在收到一个EOF字符作为唯一输入时的动作。如果设置,该值是在bash退出之前必须作为输入行的第一个字符输入的连续EOF字符的数量。如果变量存在,但没有数值或没有值,则默认值为 10。如果不存在,EOF表示对 shell 的输入结束。
INPUTRC(输入 RC)
readline 启动文件的文件名,覆盖默认的~/.inputrc(参见bash手册页中的READLINE)。
语言
用于为任何没有使用以LC_开头的变量专门选择的类别确定区域设置类别。
LC_ALL
这个变量覆盖了LANG的值和任何其他指定地区类别的LC_变量。
LC _ 校对
该变量确定对路径名扩展的结果进行排序时使用的排序顺序,并确定范围表达式、等价类以及路径名扩展和模式匹配中排序序列的行为。
LC_CTYPE
该变量决定了字符的解释以及路径名扩展和模式匹配中字符类的行为。
LC _ 消息
这个变量决定了用于翻译以$开头的双引号字符串的语言环境。
LC _ 数字
该变量决定了用于数字格式化的区域设置类别。
线
由select内置命令用来确定打印选择列表的列长度。这在收到SIGWINCH时自动设置。
邮件
如果该参数设置为一个文件名,并且没有设置MAILPATH变量,bash通知用户指定文件中的邮件到达。
邮件检查
指定bash 检查邮件的频率(秒)。默认值为 60 秒。当需要检查邮件时,shell 会在显示主要提示之前进行检查。如果该变量未设置或设置为不大于或等于零的值,shell 将禁用邮件检查。
邮件路径
要检查邮件的用冒号分隔的文件名列表。邮件到达特定文件时要打印的消息可以通过用?将文件名与消息分开来指定。在消息文本中使用时,$ _ 扩展为当前邮件文件的名称。这里有一个例子:
MAILPATH='/var/mail/bfox?"You have mail":~/shell-mail?"$_ has mail!"'
Bash为此变量提供默认值,但它使用的用户邮件文件的位置取决于系统(例如,/var/mail/$USER)。
奥斯特尔
如果设置为值 1,bash 显示由getopts内置命令生成的错误消息(参见bash手册页中的“Shell 内置命令”)。OPTERR在每次调用 shell 或执行 shell 脚本时初始化为 1。
小路
命令的搜索路径。它是一个用冒号分隔的目录列表,shell 在其中查找命令(参见bash手册页中的“命令执行”)。PATH值中的零长度(null)目录名表示当前目录。空目录名可能显示为两个相邻的冒号,或者显示为开头或结尾的冒号。默认路径取决于系统,由安装bash的管理员设置。常见的值是/usr/gnu/bin:/usr/local/bin:/usr/ucb:/bin:/usr/bin。
POSIXLY_CORRECT
如果这个变量在bash启动时处于环境中,那么 shell 在读取启动文件之前进入 POSIX 模式,就好像已经提供了--posix调用选项一样。如果它在 shell 运行时被设置,bash启用 POSIX 模式,就像命令set -o posix已经被执行一样。
提示命令
如果设置,该值在发出每个主要提示之前作为命令执行。
提示 _DIRTRIM
如果设置为大于零的数字,该值将用作展开\w和\W提示字符串转义时要保留的尾随目录组件的数量(请参见bash手册页中的“提示”)。删除的字符将替换为省略号。
PS1
该参数的值被扩展(参见bash手册页中的“提示”),并被用作主要提示字符串。默认值为"\s-\v\$ "。
PS2
该参数的值与PS1一样被扩展,并用作二级提示字符串。默认为"> "。
PS3
该参数的值用作 select 命令的提示(参见前面的“SHELL 语法”)。
PS4
该参数的值与PS1一样被扩展,并且该值在执行跟踪期间每个命令bash显示之前被打印。根据需要,PS4的第一个字符被重复多次,以表示多层次的间接性。默认为"+ "。
壳
shell 的完整路径名保存在这个环境变量中。如果在 shell 启动时没有设置,则bash会为其分配当前用户登录 shell 的完整路径名。
时间格式
该参数的值是用作格式字符串的,指定应该如何显示以时间保留字为前缀的管道的定时信息。%字符引入了一个转义序列,该序列被扩展为一个时间值或其他信息。转义序列及其含义如下:大括号表示可选部分。
%%:一个文字%。%[p][l]R:经过的时间,以秒为单位。%[p][l]U:用户模式下花费的 CPU 秒数。%[p][l]S:系统模式下花费的 CPU 秒数。%P:CPU 百分比,计算方式为(%U + %S) / %R)。可选的p是一个指定精度的数字,小数点后的小数位数。值 0 表示不输出小数点或分数。最多可以指定小数点后三位;大于 3 的p值改为 3。如果未指定p,则使用值 3。可选的 l 指定了一个更长的格式,包括分钟,格式为MMmSS.FFs。p的值决定了是否包含该分数。如果没有设置这个变量,bash的行为就好像它有值$'\nreal\t%3lR\nuser\t%3lU\nsys%3lS'一样。如果该值为 null,则不显示任何计时信息。当显示格式字符串时,会添加一个尾随换行符。
TMOUT(数字量输出)
如果设置为大于零的值, TMOUT被视为读取内置的默认超时。当输入来自终端时,如果在TMOUT秒后输入没有到达,选择命令终止。在交互式 shell 中,该值被解释为发出主要提示后等待输入的秒数。Bash如果输入没有到达,等待该秒数后终止。
设置临时文件夹
如果设置了,bash使用它的值作为目录的名称,在这个目录中bash创建临时文件供 shell 使用。
自动恢复
该变量控制 shell 如何与用户和作业控制交互。如果设置了此变量,没有重定向的单个单词简单命令将被视为恢复现有已停止作业的候选命令。不允许有任何歧义;如果有多个以键入的字符串开头的作业,则选择最近访问的作业。在此上下文中,已停止作业的名称是用于启动它的命令行。如果设置为值 exact,则提供的字符串必须与停止的作业的名称完全匹配。如果设置为 substring,则提供的字符串需要与已停止作业的名称的子字符串匹配。子字符串值提供类似于%?作业标识符的功能(参见bash手册页中的“作业控制”)。如果设置为任何其他值,则提供的字符串必须是已停止作业名称的前缀。这提供了类似于%string工作标识符的功能。
历史人物
控制历史扩展和标记化的两三个字符(参见bash手册页中的“历史扩展”)。第一个字符是历史扩展字符,该字符表示历史扩展的开始,通常为!。第二个字符是快速替换字符,用作重新运行之前输入的命令的简写,用命令中的一个字符串替换另一个字符串。默认是^。可选的第三个字符是当作为单词的第一个字符时,指示该行的剩余部分是注释的字符,通常是#。历史注释字符导致跳过对该行中剩余单词的历史替换。这并不一定会导致 shell 解析器将该行的其余部分视为注释。