tput

tput命令将通过 terminfo 数据库对您的终端会话进行初始化和操作。通过使用 tput,您可以更改几项终端功能,如移动或更改光标、更改文本属性,以及清除终端屏幕的特定区域。

什么是 terminfo 数据库?

UNIX 系统上的 terminfo 数据库用于定义终端和打印机的属性及功能,包括各设备(例如,终端和打印机)的行数和列数以及要发送至该设备的文本的属性。UNIX 中的几个常用程序都依赖 terminfo 数据库提供这些属性以及许多其他内容,其中包括 viemacs 编辑器以及 curses 和 man 程序。

与 UNIX 中的大多数命令一样,tput 命令既可以用在 shell 命令行中也可以用在 shell 脚本中。为让您更好地理解 tput,本文首先从命令行讲起,然后紧接着讲述 shell 脚本示例。

光标属性

在 UNIX shell 脚本中或在命令行中,移动光标或更改光标属性可能是非常有用的。有些情况下,您可能需要输入敏感信息(如密码),或在屏幕上两个不同的区域输入信息。在此类情况下,使用 tput 可能会对您有所帮助。

tput clear # 清屏
tput sc # 保存当前光标位置
tput cup 10 13 # 将光标移动到 row col
tput civis # 光标不可见
tput cnorm # 光标可见
tput rc # 显示输出
exit 0

移动光标

使用 tput 可以方便地实现在各设备上移动光标的位置。通过在 tput 中使用 cup 选项,或光标位置,您可以在设备的各行和各列中将光标移动到任意 X 或 Y 坐标。设备左上角的坐标为 (0,0)。

要在设备上将光标移动到第 5 列 (X) 的第 1 行 (Y),只需执行 tput cup 5 1。另一个示例是 tput cup 23 45,此命令将使光标移动到第 23 列上的第 45 行。

移动光标并显示信息

另一种有用的光标定位技巧是移动光标,执行用于显示信息的命令,然后返回到前一光标位置:

(tput sc ; tput cup 23 45 ; echo “Input from tput/echo at 23/45” ; tput rc)

下面我们分析一下 subshell 命令:

tput sc

必须首先保存当前的光标位置。要保存当前的光标位置,请包括 sc 选项或“save cursor position”。

tput cup 23 45

在保存了光标位置后,光标坐标将移动到 (23,45)。

echo “Input from tput/echo at 23/45”

将信息显示到 stdout 中。

tput rc

在显示了这些信息之后,光标必须返回到使用 tput sc 保存的原始位置。要使光标返回到其上次保存的位置,请包括 rc 选项或“restore cursor position”。

注意:由于本文首先详细介绍了通过命令行执行 tput,因此您可能会觉得在自己的 subshell 中执行命令要比单独执行每条命令然后在每条命令执行之前显示提示更简洁。

更改光标的属性

在向某一设备显示数据时,很多时候您并不希望看到光标。将光标转换为不可见可以使数据滚动时的屏幕看起来更整洁。要使光标不可见,请使用 civis 选项(例如,tput civis)。在数据完全显示之后,您可以使用 cnorm 选项将光标再次转变为可见。

文本属性

更改文本的显示方式可以让用户注意到菜单中的一组词或警惕用户注意某些重要的内容。您可以通过以下方式更改文本属性:使文本加粗、在文本下方添加下划线、更改背景颜色和前景颜色,以及逆转颜色方案等。

要更改文本的颜色,请使用 setb 选项(用于设置背景颜色)和 setf 选项(用于设置前景颜色)以及在 terminfo 数据库中分配的颜色数值。通常情况下,分配的数值与颜色的对应关系如下,但是可能会因 UNIX 系统的不同而异:

  • 0:黑色
  • 1:蓝色
  • 2:绿色
  • 3:青色
  • 4:红色
  • 5:洋红色
  • 6:黄色
  • 7:白色

执行以下示例命令可以将背景颜色更改为黄色,将前景颜色更改为红色:

tput setb 6 tput setf 4

要反显当前的颜色方案,只需执行tput rev

有时,仅为文本着色还不够,也就是说,您想要通过另一种方式引起用户的注意。可以通过两种方式达到这一目的:一是将文本设置为粗体,二是为文本添加下划线。

要将文本更改为粗体,请使用 bold 选项。要开始添加下划线,请使用 smul 选项。在完成显示带下划线的文本后,请使用 rmul 选项。

实例

使输出的字符串有颜色,底色,加粗:

#!/bin/bash
printf $(tput setaf 2; tput bold)'color show\n\n'$(tput sgr0)

for((i=0; i<=7; i++)); do
    echo $(tput setaf $i)"show me the money"$(tput sgr0)
done

printf '\n'$(tput setaf 2; tput setab 0; tput bold)'background color show'$(tput sgr0)'\n\n'

for((i=0,j=7; i<=7; i++,j--)); do
    echo $(tput setaf $i; tput setab $j; tput bold)"show me the money"$(tput sgr0)
done

exit 0

输出格式控制函数:

#!/bin/bash

# $1 str       print string
# $2 color     0-7 设置颜色
# $3 bgcolor   0-7 设置背景颜色
# $4 bold      0-1 设置粗体
# $5 underline 0-1 设置下划线

function format_output(){
    str=$1
    color=$2
    bgcolor=$3
    bold=$4
    underline=$5
    normal=$(tput sgr0)

    case "$color" in
        0|1|2|3|4|5|6|7)
            setcolor=$(tput setaf $color;) ;;
        *)
            setcolor="" ;;
    esac

    case "$bgcolor" in
        0|1|2|3|4|5|6|7)
            setbgcolor=$(tput setab $bgcolor;) ;;
        *)
            setbgcolor="" ;;
    esac

    if [ "$bold" = "1" ]; then
        setbold=$(tput bold;)
    else
        setbold=""
    fi

    if [ "$underline" = "1" ]; then
        setunderline=$(tput smul;)
    else
        setunderline=""
    fi

    printf "$setcolor$setbgcolor$setbold$setunderline$str$normal\n"
}

format_output "Yesterday Once more" 2 5 1 1

exit 0

光标属性例子:

#!/bin/bash
# clear the screen
tput clear
# Move cursor to screen location X,Y (top left is 0,0)
tput cup 3 15
# set a foreground colour using ANSI escape
tput setaf 3
echo "XYX Corp LTD."
tput sgr0
tput cup 5 17
# Set reverse video mode
tput rev
echo "M A I N - M E N U"
tput sgr0
tput cup 7 15
echo "1. User Management"
tput cup 8 15
echo "2. service Management"
tput cup 9 15
echo "3. Process Management"
tput cup 10 15
echo "4. Backup"
# Set bold mode
tput bold
tput cup 12 15
read -p "Enter your choice [1-4] " choice
tput clear
tput sgr0
tput rc

exit 0

seq

seq命令用于产生从某个数到另外一个数之间的所有整数。

语法

seq [选项]... 尾数
seq [选项]... 首数 尾数
seq [选项]... 首数 增量 尾数

选项

-f, --format=格式        使用printf 样式的浮点格式
-s, --separator=字符串   使用指定字符串分隔数字(默认使用:\n)
-w, --equal-width        在列前添加0 使得宽度相同

实例

-f选项:指定格式

#seq -f"%3g" 9 11
9
10
11

%后面指定数字的位数 默认是%g%3g那么数字位数不足部分是空格。

#sed -f"%03g" 9 11
#seq -f"str%03g" 9 11
str009
str010
str011

这样的话数字位数不足部分是0,%前面制定字符串。

-w选项:指定输出数字同宽

seq -w 98 101
098
099
100
101

不能和-f一起用,输出是同宽的。

-s选项:指定分隔符(默认是回车)

seq -s" " -f"str%03g" 9 11
str009 str010 str011

要指定/t做为分隔符号:

seq -s"`echo -e "/t"`" 9 11

指定/n/n作为分隔符号:

seq -s"`echo -e "/n/n"`" 9 11
19293949596979899910911

得到的是个错误结果,不过一般也没有这个必要,它默认的就是回车作为分隔符。

let

let命令是bash中用于计算的工具,提供常用运算符还提供了方幂**运算符。在变量的房屋计算中不需要加上$来表示变量,如果表达式的值是非0,那么返回的状态值是0;否则,返回的状态值是1。

语法

let arg [arg ...]    #arg代表运算式

用法

自加操作let no++
自减操作let no--
简写形式let no+=10let no-=20,分别等同于let no=no+10,let no=no-20

实例

#!/bin/bash
let a=5+4 b=9-3
echo $a $b
#!/bin/bash
let "t1 = ((a = 5 + 3, b = 7 - 1, c = 15 - 4))"
echo "t1 = $t1, a = $a, b = $b"

trap

trap命令用于指定在接收到信号后将要采取的动作,常见的用途是在脚本程序被中断时完成清理工作。当shell接收到sigspec指定的信号时,arg参数(命令)将会被读取,并被执行。例如:

trap "exit 1" HUP INT PIPE QUIT TERM

表示当shell收到HUP INT PIPE QUIT TERM这几个命令时,当前执行的程序会读取参数“exit 1”,并将它作为命令执行。

语法

trap [-lp] [[arg] sigspec ...]

选项参数说明

如果arg参数缺省或者为“-”,每个接收到的sigspec信号都将会被重置为它们进入shell时的值;

如果arg是空字符串每一个由sigspec指定的信号都会被shell和它所调用的命令忽略;

如果有-p选项而没有提供arg参数则会打印所有与sigspec指定信号相关联的的trap命令;

如果没有提供任何参数或者仅有-p选项,trap命令将会打印与每一个信号有关联的命令的列表;

-l选项的作用是让shell打印一个命令名称和其相对应的编号的列表。

每个sigspec信号都是是以名字或者编号的形式定义在signal.h头文件中,信号的名字是不区分大小写的,其前缀SIG是可选的,如果某个信号是 EXIT(0),那么arg指定的命令将会在shell上执行退出命令时执行(If a sigspec is EXIT (0) the command arg is executed on exit from the shell),如果sigspec是DEBUG,那么arg指定的命令将会在以下每个命令执行之前执行:

简单命令,for语句,case语句,select命令,算法命令,在函数内的第一条命令。

更多trap debug的使用可以参考extdebug选项说明。

如果sigspec是ERR,arg参数指定的命令将会在任何简单命名执行完后返回值为非零值时执行,但是也有以下例外情况:

  1. 如果执行失败的命令是紧跟在while或者until关键字之后的一组命令中的一部分时
  2. 如果执行失败的命令是if测试语句的一部分时,是 && 和 ||连接的列表中的一部分时
  3. 如果执行失败的命令的返回值是被取反过的(通过!操作符)

在以上情况中如果sigspec是ERR,arg命令不会执行,这些规则同样适用于errexit选项。如果sigspec是RETURN,arg指定的命令在每次shell函数或者脚本用"."或者内置的命令执行完成后执行,在shell入口处被忽略的命令 是没法被trap和reset的,被trap的信号,在创建的子进程中使用时会在子进程被创建时被重置为原始的值。如果trap使用的sigspec信号 是invalid的信号则trap命令返回false(失败),否则返回成功(true)。

信号

信号是一种进程间通信机制,它给应用程序提供一种异步的软件中断,使应用程序有机会接受其他程序活终端发送的命令(即信号)。应用程序收到信号后,有三种处理方式:忽略默认,或捕捉。进程收到一个信号后,会检查对该信号的处理机制。如果是SIG_IGN,就忽略该信号;如果是SIG_DFT,则会采用系统默认的处理动作,通常是终止进程或忽略该信号;如果给该信号指定了一个处理函数(捕捉),则会中断当前进程正在执行的任务,转而去执行该信号的处理函数,返回后再继续执行被中断的任务。

在有些情况下,我们不希望自己的shell脚本在运行时刻被中断,比如说我们写得shell脚本设为某一用户的默认shell,使这一用户进入系统后只能作某一项工作,如数据库备份, 我们可不希望用户使用Ctrl c之类便进入到shell状态,做我们不希望做的事情。这便用到了信号处理。

以下是一些你可能会遇到的,要在程序中使用的更常见的信号:

信号名称 信号数 描述
SIGHUP 1 本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。 登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进程组和后台有终端输出的进程就会中止。对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
SIGINT 2 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl C)时发出。
SIGQUIT 3 和SIGINT类似, 但由QUIT字符(通常是Ctrl /)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。
SIGFPE 8 在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。
SIGKILL 9 用来立即结束程序的运行. 本信号不能被阻塞, 处理和忽略。
SIGALRM 14 时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号。
SIGTERM 15 程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理. 通常用来要求程序自己正常退出. shell命令kill缺省产生这个信号。

捕获信号

当你按下 Ctrl + C 键或 Break 键在终端一个shell程序的执行过程中,正常程序将立即终止,并返回命令提示符。这可能并不总是可取的。例如,你可能最终留下了一堆临时文件,将不会清理。

捕获这些信号是很容易的,trap命令的语法如下:

$ trap commands signals

这里的命令可以是任何有效的Linux命令,或一个用户定义的函数,信号可以是任意数量的信号,你想来捕获的列表。

在shell脚本中的陷阱有三种常见的用途:

  1. 清理临时文件
  2. 忽略信号

1、清理临时文件:

trap命令作为一个例子,下面展示了如何可以删除一些文件,然后退出,如果有人试图从终端中止程序:

trap "rm -f $WORKDIR/work1$$ $WORKDIR/dataout$$; exit" 2

执行shell程序,这个陷阱的角度,这两个文件work1$$ 和 dataout$$将被自动删除,如果程序接收信号数为2。

因此,用户中断执行,如果执行的程序后,这个陷阱你可以放心,这两个文件将被清理。 exit 命令如下 rm 是必要的,因为没有它的执行将继续在节目中的一点,它离开时收到信号。

1号信号产生挂断:要么有人故意挂断线路或线路被意外断开。

您可以修改前面的陷阱也删除指定的文件,在这种情况下,两个信号信号1号添加到列表:

$ trap "rm $WORKDIR/work1$$ $WORKDIR/dataout$$; exit" 1 2

现在,这些文件将被删除,如果该行被挂了,或者按Ctrl c键被按下。

来捕获指定的命令必须用引号括起来,如果它们包含一个以上的命令。另外请注意,在 shell 命令行扫描 trap 命令得到执行,并再次当一个所列出的的信号被接收的时间。

WORKDIR 值 $$ 所以在前面的例子中,将被取代 trap 命令执行的时间。如果你想这种替代发生在收到信号1或2的时间你可以把单引号内的命令:

$ trap 'rm $WORKDIR/work1$$ $WORKDIR/dataout$$; exit' 1 2

2、忽略信号:

如果陷阱列出的命令是空的,指定的信号接收时,将被忽略。例如,下面的命令:

$ trap '' 2

指定的中断信号是被忽略的。你可能要忽略某些信号时进行一些操作,不希望打断。可以指定多个信号被忽略如下:

$ trap '' 1 2 3 15

注意,第一个参数必须被指定为一个信号被忽略,而不是相当于写入下面的内容,它具有独立的含义也各有:

$ trap  2

如果你忽略了一个信号,所有的子shell也忽略该信号。不过,如果指定要采取的行动在收到的信号,所有的子shell仍然会在收到该信号的默认操作。

3、重设陷阱:

当你改变了默认在收到信号后应采取的动作,你可以改变它回来的陷阱,如果你只是省略第一个参数;

$ trap 1 2

复位应采取的动作收到信号1或2返回默认。