15、数据处理与屏幕脚本编写全解析

数据处理与屏幕脚本编写全解析

数据处理

在数据处理领域,有多种方式对数据进行操作和解析。

分隔记录处理

对于分隔记录,我们可以使用之前定义的记录进行操作。例如:

$ split_record "$record" : user passwd uid gid name homedir shell
$ sa "$user" "$passwd" "$uid" "$gid" "$name" "$homedir" "$shell"
:root:
:x:
:0:
:0:
:root:
:/root:
:/bin/bash:

这里通过 split_record 函数将记录按冒号分隔,并将各部分赋值给相应变量,然后使用 sa 函数处理这些变量。

定长字段解析

定长字段不像分隔字段那样常见,但在某些场景下也会用到。解析定长字段时,会遍历 name=width 字符串来进行解析,许多文本编辑器从定长字段数据文件导入数据就是采用这种方式。示例代码如下:

line="John           123 Fourth Street   Toronto     Canada                "
for nw in name=15 address=20 city=12 country=22
do
  var=${nw%%=*}                 ## 变量名在等号之前
  width=${nw#*=}                ## 字段宽度在等号之后
  eval "$var=\${line:0:width}"  ## 提取字段
  line=${line:width}            ## 从记录中移除已提取的字段
done

在这个代码中,通过循环遍历 name=width 列表,依次提取定长字段并赋值给相应变量。

块文件格式处理 - PGN 国际象棋文件

PGN(Portable Game Notation)国际象棋文件是一种常见的块数据文件。它以人类和机器都可读的格式存储一个或多个国际象棋游戏。每个游戏以七个标签开始,用于标识游戏的时间、地点、参与者和结果,接着是一个空行,然后是游戏的走法。示例 PGN 文件如下:

[Event "ICS rated blitz match"]
[Site "69.36.243.188"]
[Date "2009.06.07"]
[Round "-"]
[White "torchess"]
[Black "FidelCastro"]
[Result "1-0"]

1. f4 c5 2. e3 Nc6 3. Bb5 Qc7 4. Nf3 d6 5. b3 a6 6. Bxc6+ Qxc6 7. Bb2 Nf6
8. O-O e6 9. Qe1 Be7 10. d3 O-O 11. Nbd2 b5 12. Qg3 Kh8 13. Ne4 Nxe4 14.
Qxg7#
{FidelCastro checkmated} 1-0

要解析 PGN 文件,可以使用 while 循环读取标签,然后使用 mapfile 获取游戏的走法。以下是解析 PGN 文件的代码:

pgnfile="${1:?}"
header=0
game=0

gettag() #@ 创建与标签同名且值相同的变量
{
  local tagline=$1
  tag=${tagline%% *}        ## 获取第一个空格之前的内容
  tag=${tag#?}              ## 移除开头的方括号
  IFS='"' read a val b <<.  ## 使用双引号作为分隔符获取第二个字段
   $tagline
.

  eval "$tag=\$val"
}

{
  while IFS= read -r line
  do
    case $line in
      \[*) gettag "$line" ;;
      "") [ -n "$Event" ] && break;;  ## 跳过文件开头的空行
    esac
  done
  mapfile -t game                     ## 读取文件的剩余部分
} < "$pgnfile"

## 移除数组末尾的空行
while [ -z "${game[${#game[@]}-1]}" ]
do
  unset game[${#game[@]}-1]
done

## 打印带有标题的游戏信息
echo "Event: $Event"
echo "Date:  $Date"
echo
set -f
printf "%4s  %-10s %-10s\n" "" White Black  ""  ========== ========== \
          "" "$White" "$Black" ${game[@]:0:${#game[@]}-1}
printf "%s\n" "${game[${#game[@]}-1]}"

解析 PGN 文件的流程如下:

graph TD;
    A[开始] --> B[读取 PGN 文件路径];
    B --> C[初始化变量];
    C --> D[循环读取文件行];
    D --> E{行是否以 [ 开头};
    E -- 是 --> F[调用 gettag 函数提取标签值];
    E -- 否 --> G{行是否为空};
    G -- 是且 Event 变量有值 --> H[跳出循环];
    G -- 否 --> D;
    F --> D;
    H --> I[使用 mapfile 读取文件剩余部分];
    I --> J[移除数组末尾的空行];
    J --> K[打印游戏信息];
    K --> L[结束];
屏幕脚本编写

传统上,屏幕操作通过 termcap terminfo 数据库进行,其 shell 接口是外部命令 tput 。但由于终端类型逐渐统一为 ISO 6429(也称为 ECMA - 48,以前称为 ANSI X3.64 或 VT100)标准,现在可以直接在 shell 中进行屏幕操作。

屏幕操作模式

有两种将脚本输出发送到终端屏幕的方法:
- 电传打字机模式 :将终端视为打印机或电传打字机,每打印一行,屏幕图像向上滚动,旧行消失。这种模式简单,适用于许多应用场景。
- 画布模式 :将屏幕视为黑板或画布,可以在屏幕的特定位置打印内容,擦除和覆盖之前的内容,还可以按列或在特定位置打印文本,使终端成为随机访问设备。

画布操作基础

要将屏幕用作画布,最重要的是能够将光标定位到屏幕上的任何位置。定位光标的序列是 ESC[<ROW>;<COL>H ,转换为 printf 格式字符串后可以直接使用或封装在函数中。示例代码如下:

cu_row_col=$'\e[%d;%dH'
printf "$cu_row_col" 5 10  ## 第 5 行,第 10 列
echo "Here I am!"

以下是一些屏幕操作的函数和变量定义:

# screen-vars 文件
ESC=$'\e'
CSI=$ESC[

# 移动光标到屏幕左上角
topleft=${CSI}H
# 清屏
cls=${CSI}J
# 清屏并移动到屏幕左上角
clear=$topleft$cls
# 清除从光标到行尾的内容
clearEOL=${CSI}K
# 清除从光标到行首的内容
clearBOL=${CSI}1K
# 清除从光标到屏幕末尾的内容
clearEOS=${CSI}0J
# 清除从光标到屏幕开头的内容
clearBOS=${CSI}1J

# 光标移动字符串
cu_up=${CSI}%sA
cu_down=${CSI}%sB
cu_right=${CSI}%sC
cu_left=${CSI}%sD

# 关闭和打开光标
cu_hide=${CSI}?25l
cu_show=${CSI}?12l${CSI}?25h

# 保存光标位置
cu_save=${CSI}s
# 恢复光标到保存的位置
cu_restore=${CSI}u

# 移动光标到块中的下一行/上一行
cu_NL=$cu_restore${cu_down/\%s/}$cu_save
cu_PL=$cu_restore${cu_up/\%s/}$cu_save

这些变量和函数提供了基本的屏幕操作能力,如清屏、移动光标、保存和恢复光标位置等。

屏幕操作示例 - screen-demo1
. screen-funcs                             ## 引入屏幕操作函数库
printf "$clear$cu_hide"                    ## 清屏并隐藏光标
printat 10 10 "${cu_save}XX"               ## 移动光标、保存位置并打印 XX
sleep 1                                    ## 暂停 1 秒
printat 20 20 "20/20"                      ## 移动光标并打印
sleep 1                                    ## 暂停 1 秒
printf "$cu_restore$cu_down${cu_save}YY"   ## 恢复位置、移动光标、打印并保存位置
sleep 1                                    ## 暂停 1 秒
printf "$cu_restore$cu_down${cu_save}ZZ" 4 ## 恢复位置、移动光标、打印并保存位置
sleep 1                                    ## 暂停 1 秒
printat 1 1 "$cu_show"                     ## 移动到屏幕左上角并显示光标

在这个示例中,通过调用 printat 函数和使用光标移动、保存和恢复的变量,实现了在屏幕上不同位置打印内容并控制光标的操作。

颜色和属性设置

字符可以以粗体、下划线、反显等模式以及各种颜色打印(前提是终端支持)。这些属性通过 ESC[ATTRm 序列进行修改,其中 ATTR 是属性或颜色的编号。以下是颜色和属性的变量定义:

# 颜色定义
black=0
red=1
green=2
yellow=3
blue=4
magenta=5
cyan=6
white=7

# 前景色和背景色前缀
fg=3
bg=4

# 属性定义
bold=1
underline=4
reverse=7

# 设置颜色
set_bg="${CSI}4%dm"          ## 设置背景颜色
set_fg="${CSI}3%dm"          ## 设置前景颜色
set_fgbg="${CSI}3%d;4%dm"    ## 设置前景和背景颜色

以下是一个颜色和属性设置的示例脚本:

. 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 "\e[m\n"
done
echo

在这个脚本中,通过循环设置不同的属性和颜色,打印出不同颜色和样式的文本。

文本块放置

put_block 函数可以在当前光标位置逐行打印参数, put_block_at 函数可以将光标移动到指定位置,然后调用 put_block 函数打印参数。示例代码如下:

put_block() #@ 在当前位置开始以块的形式打印参数
{
  printf "$cu_save"      ## 保存光标位置
  printf "%s$cu_NL" "$@" ## 恢复光标位置、向下移动一行并保存光标
}

put_block_at() #@ 在 $1 和 $2 指定的位置以块的形式打印参数
{
  printat "$1" "$2"
  shift 2
  put_block "$@"
}

以下是使用 put_block_at 函数的示例脚本:

. 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

这个脚本在屏幕上以列的形式显示数据块。

文本换行

当要打印的文本是一个字符串时,可以使用 wrap 函数将其拆分为不超过指定最大宽度的行。示例代码如下:

wrap() #@ 用法:wrap 字符串 长度
{      #@ 需要 bash - 3.1 或更高版本
  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} )) ## 测试添加一个单词后的长度
    if [ "$templen" -gt "$textwidth" ] ## 添加单词是否超过长度限制?
    then
      wrap+=( "$line" )                ## 是,将当前行存储到数组中
      printf -v line "%s" "$word"      ## 开始新的一行
      len=${#word}
    else
      len=$templen                     ## 否,将单词添加到当前行
      printf -v line "%s" "${line:+"$line "}" "$word"
    fi
  done
  wrap+=( "$line" )

  case $opts in
    *f*) ;;
    *) set +f ;;
  esac
}

以下是使用 wrap 函数的示例脚本:

clear
wrap "The quick brown fox jumps over the lazy dog" 15
x=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
printat 1 1
printf "%s\n" $x{,,,,,,,,,,}          ## 打印 11 行 'x'
print_block_at 3 33 "${wrap[@]}"
printat 12 1

这个脚本将字符串拆分为不超过 15 个字符的行,并在屏幕上打印出来。

文本滚动

通过结合数组和子字符串扩展,可以在屏幕的任何区域滚动文本。示例代码如下:

list=( /usr/bin/* )          ## 可以尝试其他目录或列表
rows=9                       ## 滚动区域的行数
delay=.01                    ## 滚动间隔时间
width=-33.33                 ## 宽度规格:(不超过)33 个字符,左对齐
x=XXXXXXXXXXXXXXXXXXXXXXXXXX ## 'X' 组成的条
x=$x$x$x$x                   ## 更长的条

clear                        ## 清屏
printf "%50.50s\n" $x{,,,,,,,,,,,,,}          ## 打印 14 行 'X'

n=0                          ## 从第一个元素开始显示

## 向上滚动直到到达底部
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

## 向下滚动直到到达顶部
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 "\e[15;1H"    ## 结束时将光标移到滚动区域下方

在这个脚本中,将 /usr/bin/ 目录下的文件名存储在数组中,然后在屏幕上滚动显示这些文件名。

骰子绘制

使用 shell 可以轻松实现骰子的图形化显示。以下是一个绘制骰子的示例代码:

pip=o                      ## 骰子点数的字符
p0="       "               ## 空白行
p1=" $pip     "            ## 左侧一个点数
p2="   $pip   "            ## 中间一个点数
p3="     $pip "            ## 右侧一个点数
p4=" $pip   $pip "         ## 两个点数
p5=" $pip $pip $pip "      ## 三个点数

cs=$'\e7'                  ## 保存光标位置
cr=$'\e8'                  ## 恢复光标位置
dn=$'\e[B'                 ## 向下移动一行
b=$'\e[1m'                 ## 设置粗体属性
cu_put='\e[%d;%dH'         ## 定位光标的格式字符串
fgbg='\e[3%d;4%dm'         ## 设置颜色的格式字符串

dice=(
  ## 点数为 1 到 6 的骰子(数组元素 0 到 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               ## 定位光标
printf "$fgbg" 7 0                 ## 白色前景,黑色背景
printf "%s\n" "${dice[RANDOM%6]}"  ## 打印随机骰子
printf "$cu_put" 2 20              ## 定位光标
printf "$fgbg" 0 3                 ## 黑色前景,黄色背景
printf "%s\n" "${dice[RANDOM%6]}"  ## 打印随机骰子

在这个脚本中,定义了一个包含六个骰子图形的数组,通过随机选择数组元素并设置颜色和位置,在屏幕上打印出两个随机骰子。

通过以上介绍,我们了解了数据处理和屏幕脚本编写的多种方法和技巧,这些技术可以帮助我们更好地处理数据和实现交互式的屏幕显示。

数据处理与屏幕脚本编写全解析

数据处理与屏幕脚本编写的综合应用与拓展
数据处理的拓展应用

在实际应用中,数据处理的需求往往更加复杂。例如,对于数组操作,我们可以对之前提到的排序和搜索函数进行优化。当数组规模超过一定大小时,可以使用 sort grep 等工具来提高效率。以下是一个简单的思路:

isort() {
    local arr=("$@")
    if [ ${#arr[@]} -gt 100 ]; then
        printf "%s\n" "${arr[@]}" | sort
    else
        # 原排序算法
        for ((i = 0; i < ${#arr[@]}; i++)); do
            for ((j = i + 1; j < ${#arr[@]}; j++)); do
                if [[ "${arr[$i]}" > "${arr[$j]}" ]]; then
                    tmp="${arr[$i]}"
                    arr[$i]="${arr[$j]}"
                    arr[$j]="$tmp"
                fi
            done
        done
        printf "%s\n" "${arr[@]}"
    fi
}

asearch() {
    local arr=("$@")
    local search_term="${arr[-1]}"
    unset arr[-1]
    if [ ${#arr[@]} -gt 100 ]; then
        printf "%s\n" "${arr[@]}" | grep "$search_term"
    else
        # 原搜索算法
        for element in "${arr[@]}"; do
            if [[ "$element" == *"$search_term"* ]]; then
                echo "$element"
            fi
        done
    fi
}

另外,对于网格数据的处理,我们可以编写一个函数来转置网格的行和列。以下是实现代码:

transpose_grid() {
    local grid=("$@")
    local rows=${#grid[@]}
    local cols=$((${#grid[0]} / 1))  # 假设每个元素长度为 1
    local transposed=()
    for ((j = 0; j < cols; j++)); do
        local new_row=""
        for ((i = 0; i < rows; i++)); do
            new_row="$new_row${grid[$i]:$j:1}"
        done
        transposed+=("$new_row")
    done
    printf "%s\n" "${transposed[@]}"
}

# 示例使用
grid=("123" "456" "789")
transpose_grid "${grid[@]}"

还可以将网格处理函数扩展到非正方形网格,例如 6×3 的网格。以下是一个简单的示例:

process_non_square_grid() {
    local grid=("$@")
    local rows=${#grid[@]}
    local cols=$((${#grid[0]} / 1))
    # 处理非正方形网格的逻辑
    for ((i = 0; i < rows; i++)); do
        for ((j = 0; j < cols; j++)); do
            printf "%s " "${grid[$i]:$j:1}"
        done
        printf "\n"
    done
}

# 示例使用
non_square_grid=("123" "456" "789" "abc" "def" "ghi")
process_non_square_grid "${non_square_grid[@]}"
屏幕脚本编写的拓展应用

在屏幕脚本编写方面,我们可以进一步优化和拓展之前的功能。例如,对于 put_block put_block_at 函数,当屏幕上已经有很多文本时,输出可能会被遮挡。我们可以使用 print_block print_block_at 函数来清除文本块周围的矩形区域。以下是相关代码:

_max_length() {
    local var
    _MAX_LENGTH=${#1}
    shift
    for var; do
        [ "${#var}" -gt "$_MAX_LENGTH" ] && _MAX_LENGTH=${#var}
    done
}

print_block() {
    local _MAX_LENGTH
    _max_length "$@"
    printf "$cu_save"
    printf " %-${_MAX_LENGTH}s $cu_NL" " " "$@" " "
}

print_block_at() {
    printat $1 $2
    shift 2
    print_block "$@"
}

另外,对于文本换行和滚动功能,我们可以结合起来实现更复杂的效果。例如,在滚动区域内显示换行后的文本。以下是一个示例脚本:

list=("This is a long text that needs to be wrapped and scrolled." "Another line of text for demonstration.")
rows=9
delay=.01
width=33
x=XXXXXXXXXXXXXXXXXXXXXXXXXX
x=$x$x$x$x

clear
printf "%50.50s\n" $x{,,,,,,,,,,,,,}

n=0
while [ $(( n += 1 )) -lt $(( ${#list[@]} - $rows )) ]; do
    local wrapped_lines=()
    for line in "${list[@]:n:rows}"; do
        wrap "$line" "$width"
        wrapped_lines+=("${wrap[@]}")
    done
    printf "\e[3;1H"
    for wrapped_line in "${wrapped_lines[@]}"; do
        printf "\e[7C %s\n" "$wrapped_line"
    done
    # read -sn1 -t "$delay" && break
    sleep "$delay"
done

sleep 1

while [ $(( n -= 1 )) -ge 0 ]; do
    local wrapped_lines=()
    for line in "${list[@]:n:rows}"; do
        wrap "$line" "$width"
        wrapped_lines+=("${wrap[@]}")
    done
    printf "\e[3;1H"
    for wrapped_line in "${wrapped_lines[@]}"; do
        printf "\e[7C %s\n" "$wrapped_line"
    done
    # read -sn1 -t "$delay" && break
    sleep "$delay"
done

printf "\e[15;1H"
总结

本文详细介绍了数据处理和屏幕脚本编写的相关知识和技术。在数据处理方面,涵盖了分隔记录处理、定长字段解析、块文件格式处理等内容,还给出了一些拓展应用的示例,如数组排序和搜索的优化、网格数据处理等。在屏幕脚本编写方面,介绍了屏幕操作模式、画布操作基础、颜色和属性设置、文本块放置、文本换行、文本滚动和骰子绘制等功能,并且提供了相应的代码示例和拓展应用。

通过掌握这些技术,我们可以更好地处理各种数据,实现交互式的屏幕显示,为开发更加复杂和功能强大的脚本程序奠定基础。以下是一个总结表格:

领域 主要内容 示例代码
数据处理 分隔记录处理、定长字段解析、PGN 文件处理、数组操作优化、网格处理 多个代码块展示,如 split_record 示例、 wrap 函数等
屏幕脚本编写 屏幕操作模式、画布操作、颜色属性设置、文本处理、滚动和绘制 多个代码块展示,如 printat 函数、 dice 脚本等

未来,我们可以根据实际需求,进一步拓展和优化这些技术,例如结合更多的外部工具和库,实现更高效、更复杂的数据处理和屏幕交互效果。

graph LR;
    A[数据处理] --> B[分隔记录处理];
    A --> C[定长字段解析];
    A --> D[块文件处理];
    A --> E[数组操作优化];
    A --> F[网格处理];
    G[屏幕脚本编写] --> H[屏幕操作模式];
    G --> I[画布操作];
    G --> J[颜色属性设置];
    G --> K[文本处理];
    G --> L[滚动和绘制];

通过以上的总结和拓展,我们对数据处理和屏幕脚本编写有了更全面和深入的理解,希望这些内容能对大家在实际应用中有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值