Shell脚本打印国际象棋棋盘

2017-12-28|Categories: Magedu-training, Shell script|

棋盘简介

国际象棋的棋盘由64个方格(8×8)组成:

思路

  • 首先定义如何打印单个方格:
    • 用多个带有背景颜色连续空格组成一个方格。
      • 不同的字体、终端设置会导致不同的空格宽度、行高度。
      • 我使用iTerm终端模拟器,字体是Roboto Mono,字号14pt,详见下方截图。
      • 我选择宽度为5个空格,高度为2行的组合,打印出来接近正方形。
    • 定义两种方格:红色,白色。
  • 然后定义如何打印单行:
    • 根据列号是否为偶数(或奇数),交错打印不同颜色的单个空格。
    • 总的列数由用户通过位置参数指定。
    • 因为单个方格高度为2行,每行需要打印2次。
    • 定义两种颜色组合:一种以红色方格开始,一种以白色方格开始。
  • 最后定义如何打印整个棋盘:
    • 根据行号是否为偶数(或奇数),交错打印不同颜色组合的行。
    • 总的行数由用户通过位置参数指定。

终端模拟器字体设置:

具体实现

#!/bin/bash
#
#===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====
# Filename:     chess_by_liyang.sh
# Revision:     1.0
# Date:         2017-12-26
# Description:
# Author:       Li Yang
# Website:      https://liyang85.com
#===== ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====

bgBlack=`tput setab 0`
bgRed=`tput setab 1`
bgYellow=`tput setab 3`
bgWhite=`tput setab 7`
reset=`tput sgr0`

# determine if there is a $1 parameter
if [[ -z "$1" ]]; then
    echo
    echo -e "Usage: \t`basename $0` 8"
    echo -e "\t8 means rows and columns of the chessboard are 8x8."
    exit 1
fi

# determine if the $1 is a positive integer
[[ "$1" =~ ^[0-9]+$ ]] \
    || { echo -e "\nError! Please input a positive integer." && exit 2; }

# 1 block = blkWidth_spaces x blkHeight_spaces
blkWidth=5
blkHeight=2

# determine if the specified columns exceed tty max allowed value
ttyCols=`tput cols`
maxBlks=$[${ttyCols} / $blkWidth]
[[ $1 -gt $maxBlks ]] \
    && echo -e "\nError! TTY columns are ${ttyCols}, just allows ${maxBlks} blocks per line." \
    && echo "Please give a number which less than or equal to ${maxBlks}." \
    && exit 3

blk1() {
    for j in `seq ${blkWidth}`; do
        # echo -n "${bgBlack} ${reset}"
        echo -n "${bgRed} ${reset}"
    done
}
blk2() {
    for j in `seq ${blkWidth}`; do
        echo -n "${bgWhite} ${reset}"
        # echo -n "${bgYellow} ${reset}"
    done
}

# function has its own positional parameters,
# and can't see the positional parameters of the script,
# for avoiding errors,
# if you want to pass script's positional parametes to a function:
# (1) script's positional parameters must be assigned to a variable,
# (2) then use the variable in a function.
#
cols=$1
line1() {
    for i in `seq ${blkHeight}`; do
        for j in `seq ${cols}`; do
            # echo $j   # for debugging
            if [[ $[j%2] -eq 0 ]]; then
                blk1
            else
                blk2
            fi
        done
        echo
    done
}
line2() {
    for i in `seq ${blkHeight}`; do
        for j in `seq ${cols}`; do
            if [[ $[j%2] -eq 0 ]]; then
                blk2
            else
                blk1
            fi
        done
        echo
    done
}

rows=$1
echo
for i in `seq ${rows}`; do
    if [[ $[i%2] -eq 0 ]]; then
        line1
    else
        line2
    fi
done

注意事项

设置输出到终端的颜色

输出颜色到终端可以使用ANSI硬编码,例如\033[31m代表红色前景色,\033[41m代表红色背景色,但BashFAQ推荐使用tput命令:

tput setaf 1; echo 'this is red'
tput setaf 2; echo 'this is green'
tput bold; echo 'boldface (and still green)'
tput sgr0; echo 'back to normal'

这样推荐的原因是为了写出的脚本能与更多不同种类的终端兼容。在Unix发展的早期,各家厂商开发了大量各具特色的终端,各自包括不同的转义序列,硬编码会导致脚本不兼容某些终端,而tput命令会查询terminfo数据库,读取不同终端支持的转义序列,以此保证兼容。

但另一种观点认为,目前Linux越来越流行,用户通常使用终端模拟器访问Linux主机,而不是各种物理终端,兼容问题不再像早期那样频繁出现,相反,terminfo数据库存在bug,在某些系统上会导致tput命令工作不正常,在这种情况下,硬编码反而是一个好的选择。

以上两种观点截然相反,但都是从实际情况出发,有各自的客观依据,完全不是非此即彼的关系,根据自己的使用环境和偏好选用即可。

我选tput命令,因为这种方式只需要输入拉丁字母,更简洁,不容易出错,需要记忆的颜色信息也更少(即使记不住,也可以man 5 terminfo,搜索Color Handling关键字就能找到):

Color Value
black 0
red 1
green 2
yellow 3
blue 4
magenta 5
cyan 6
white 7
  • 如果设置红色前景色,就是tput setaf 1setaf = Set ANSI Foreground。
  • 如果设置红色背景色,就是tput setab 1setab = Set ANSI Background。

tput还可以输出粗体字、添加下划线:

关键字 功能
bold 开始输出粗体字
smul 开始给文本添加下划线
rmul 停止给文本添加下划线
sgr0 关闭所有效果
  • smul = Start Mode of UnderLine
  • rmul = Remove Mode of UnderLine
  • sgr = Set GRaphical attributes

还有一些我很少使用的效果,详见linuxcommand.org

传递脚本位置参数给函数

编写shell脚本时,有时候需要把脚本的位置参数传递给脚本内的函数,例如上面脚本中,需要把用户指定的$1,也就是行数和列数传递给line1()line2()两个函数,但函数有自己的位置参数,写法和脚本一样,也是$1$2、……,如果在函数内部直接写$1,将读取一个空值,而不是传给脚本的那个$1

因此,除了表示脚本完整路径的$0其它的脚本位置参数必须先存入变量,然后在函数中调用这个变量

执行结果

Leave A Comment