第二课《shell 编程基础》

编程语言分类:

  • 编译型
  • 解释型

编译型语言:c、c++、c#

解释型语言:
内置型:shell、perl源码并入了linux kernel,提供内核级单条语句的效率
外置型:python、java、js,通过一个解释器(解释器本身的编译型语言例如c/c++写的可执行精灵程序)。外置型语言普遍比内置型语言的执行效率要高(总体上来说,单条未必)。通过预编译技术,将运行时解释的部分工作提前在运行前算好了(摘要、分类、预测),所以整体上其执行效率也非常高,其中java的执行效率接近c++,但是python的执行效率还是个渣渣。

各编程语言都有if switch for while 等与语法,也都要字符串、int、常量、数组等基本数据类型。
差异并不是特备的明显,所以一般的后端程序员,在工作过3年以上以后,任何语言都会一点。编程语言的主要差异在于:编译器的层次,和库的依赖方式。

这里提一下go语言:
golang曾经长期在解释型编程语言界混,然后由于创始人本来是写汇编编译器的,在经过市场实用几年以后,创始人将golang于他写的那个汇编器挂钩,一下提升了golang的层次,进化到了编译型编程语言。

一个程序的时间消耗:

  • 程序员编码(一次性消耗)
  • 编译(一次性消耗)
  • 执行(多次消耗)。

那么程序效率来讲,指的就是执行时的消耗。

从执行效率来看,自然是编译型的代码执行效率高效。gcc g++ 在编译时就将代码转换为汇编,进而转换为机器语码,进而调用cpu门电路(移位寄存器、累加器、比较器、乘法器)。信息传递导致延迟,几乎接近机器码的语言,大大节省了在代码执行时信息传递的时间,不过在编译时需要更多的时间。

解释型编程语言,不需要编译的时间,或需要很少的预编译(java,Python),然后到字符串解析器,解析为最小单位的api调用,让后调用操作系统。

各种类型编程语言的运行示意图:

在这里插入图片描述

Linux应用开发的课程,第一部分是linux基本操作,第二部分是shell编程(解释型编程语言),第三部分是linux c/c++编程(编译型编程语言)。本节课程是第二部分。

扫描二维码关注公众号,回复: 8854071 查看本文章

shell编程

下面以一个hello world开篇:
python脚本:

[root@izwz93atpalb56zydy9bpyz 0-操作系统介绍]# cat test.py 
#!/usr/bin/python

print('HELLO WORLD!')

[root@izwz93atpalb56zydy9bpyz 0-操作系统介绍]# ./test.py 
HELLO WORLD!
[root@izwz93atpalb56zydy9bpyz 0-操作系统介绍]# 

shell脚本

[root@izwz93atpalb56zydy9bpyz 0-操作系统介绍]# cat test.sh 
#!/bin/bash

echo "hello world"
[root@izwz93atpalb56zydy9bpyz 0-操作系统介绍]# ./test.sh 
hello world
[root@izwz93atpalb56zydy9bpyz 0-操作系统介绍]#

shell脚本的格式:

第一行固定以#!/bin/bash 或 #!/bin/sh开头,第二行开始每行一条命令。shell可以初步理解为,是你在终端输入命令的批量执行的集合。例如脚本test.sh内容为:

#!/bin/bash

pwd
ls
ifconfig

其执行效果为:

[root@izwz93atpalb56zydy9bpyz tmp]# ./test.sh 
/tmp
Aegis-<Guid(5A2C30A2-A87D-490A-9281-6765EDAD7CBA)>  redis-2.6.14	 systemd-private-6c086a98f67e41439610fd69d85c8c01-cups.service-2ntpHp
main.cc						    redis-2.6.14.tar.gz  systemd-private-6c086a98f67e41439610fd69d85c8c01-ntpd.service-WohG6p
redis						    redis-beta-3.tar.gz  test.sh
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.75.99  netmask 255.255.240.0  broadcast 172.18.79.255
        ether 00:16:3e:02:ab:7d  txqueuelen 1000  (Ethernet)
        RX packets 62221  bytes 22156779 (21.1 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 58120  bytes 53103229 (50.6 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 2009424  bytes 613177808 (584.7 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2009424  bytes 613177808 (584.7 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

virbr0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 192.168.122.1  netmask 255.255.255.0  broadcast 192.168.122.255
        ether 52:54:00:a4:29:8d  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[root@izwz93atpalb56zydy9bpyz tmp]# 

与三条命令的执行结果是一样的:
pwd的执行结果:

[root@izwz93atpalb56zydy9bpyz tmp]# pwd
/tmp
[root@izwz93atpalb56zydy9bpyz tmp]#

ls的执行结果

[root@izwz93atpalb56zydy9bpyz tmp]# ls
Aegis-<Guid(5A2C30A2-A87D-490A-9281-6765EDAD7CBA)>  redis-2.6.14         systemd-private-6c086a98f67e41439610fd69d85c8c01-cups.service-2ntpHp
main.cc                                             redis-2.6.14.tar.gz  systemd-private-6c086a98f67e41439610fd69d85c8c01-ntpd.service-WohG6p
redis                                               redis-beta-3.tar.gz  test.sh
[root@izwz93atpalb56zydy9bpyz tmp]#

ifconfig 的执行结果:

[root@izwz93atpalb56zydy9bpyz tmp]# ifconfig 
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.75.99  netmask 255.255.240.0  broadcast 172.18.79.255
        ether 00:16:3e:02:ab:7d  txqueuelen 1000  (Ethernet)
        RX packets 62266  bytes 22160411 (21.1 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 58148  bytes 53112131 (50.6 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 2009424  bytes 613177808 (584.7 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2009424  bytes 613177808 (584.7 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

virbr0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 192.168.122.1  netmask 255.255.255.0  broadcast 192.168.122.255
        ether 52:54:00:a4:29:8d  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[root@izwz93atpalb56zydy9bpyz tmp]#

各种类型变量的定义

#!/bin/bash
set -e

echo "------------------shell r/w 变量定义---------------
VAR_CHAR='A'
VAR_INT=100
VAR_STR=\"hello world\"
VAR_DOUBLE=3.141592653
readonly VAR_CONST=\"hello i am const variable\"
"

VAR_CHAR='A'
VAR_INT=100
VAR_STR="hello world"
VAR_DOUBLE=3.141592653
readonly VAR_CONST="hello i am const variable"


echo
echo "------------------shell r/w 变量读取方式1----------"
echo $VAR_CHAR
echo $VAR_INT
echo $VAR_STR
echo $VAR_DOUBLE
echo $VAR_CONST

echo
echo "------------------shell r/w 变量读取方式2----------"
echo ${VAR_CHAR}
echo ${VAR_INT}
echo ${VAR_STR}
echo ${VAR_DOUBLE}
echo ${VAR_CONST}


echo
echo "------------------shell r/w 变量赋值---------------"
VAR_CHAR=1.1111
echo ${VAR_CHAR}


echo
echo "------------------shell r/w 变量加法---------------"
echo "add method 1---"
VAR_INT=`expr $VAR_INT + 1`
echo ${VAR_INT}
echo "add method 2---"
let VAR_INT++
echo ${VAR_INT}
echo "add method 3---"
((VAR_INT++))
echo ${VAR_INT}
echo "add method 4---"
VAR_INT=$[$VAR_INT + 1]
echo ${VAR_INT}
echo "add method 5---"
VAR_INT=$(($VAR_INT + 1))
echo ${VAR_INT}


echo
echo "------------------shell r/w 变量减法---------------"
echo "sub method 1---"
VAR_INT=`expr $VAR_INT - 1`
echo ${VAR_INT}


echo
echo "------------------shell r/w 变量乘法---------------"
echo "mul method 1---"
VAR_INT=$(($VAR_INT * 4))
echo ${VAR_INT}

echo
echo "------------------shell r/w 变量除法---------------"
echo "除法 method 1---"
VAR_INT=$(($VAR_INT / 2))
echo ${VAR_INT}


echo
echo "------------------shell r/w 变量删除---------------"
unset VAR_INT
echo ${VAR_INT}


echo
echo "------------------shell r/w 字符串变量追加---------------"
echo $VAR_STR
VAR_STR=$VAR_STR"-tail string"
echo $VAR_STR

echo $VAR_CONST
#VAR_CONST=$VAR_CONST"-tail string"
echo $VAR_CONST


echo
echo "------------------shell r/w 数组的定义1---------------"
array_tbl=("jacky" "1" '2' 3 4.12222)
echo ${array_tbl[0]}
echo ${array_tbl[1]}
echo ${array_tbl[2]}
echo ${array_tbl[3]}
echo ${array_tbl[4]}


echo
echo "------------------shell r/w 数组的定义2---------------"
array_tbl2[0]="robbe"
array_tbl2[1]=123456789
array_tbl2[2]='c'
array_tbl2[3]="你好"
echo ${array_tbl2[0]}
echo ${array_tbl2[1]}
echo ${array_tbl2[2]}
echo ${array_tbl2[3]}


echo "length of array array_tbl is:" ${#array_tbl[@]}
echo "length of array array_tbl is:" ${#array_tbl[*]}
echo "length of array array_tbl2 is:" ${#array_tbl2[*]}
echo "length of array_tbl[0] is:" ${#array_tbl[0]}
echo "length of array_tbl[1] is:" ${#array_tbl[1]}
echo "length of array_tbl2[1] is:" ${#array_tbl2[1]}

程序流程控制

  • if语句
  • for循环
  • while循环

if 语句:

#!/bin/bash

echo "
if语句的语法格式:
if [ 条件 ];then
        语句
fi
"

echo "
总共有6个关系运算符:
-eq 检测两个数据相等
-ne 检测连个数据不相等
-gt 左边 > 右边
-lt 左边 < 右边
-ge 左边 >= 右边
-le 左边 <= 右边
"

VAR_INT=10

if [ $VAR_INT -eq 10 ];then
        echo "$VAR_INT 等于10"
elif [ $VAR_INT -ne 10 ];then
        echo "VAR_INT 不等于10"
fi

for语句:

#!/bin/bash

echo "
for语句的语法格式:
for (( i=0; i<变量; i++ ))do
        语句
done
"

array=(1 3 5 7 8 9 10 11 12 13) 

for i in ${array[@]}
do
        if [ $i -eq 10 ];then
                echo$i 等于10“
        fi  

        if [ $i -ne 10 ];then
                echo$i 不等于10“
        fi  

        if [ $i -gt 10 ];then
                echo$i 大于10“
        fi  

        if [ $i -lt 10 ];then
                echo$i 小于10“
        fi  
    
        if [ $i -ge 10 ];then

                echo$i  大于等于10“
        fi  

        if [ $i -le 10 ];then
    
                echo$i 小于等于10“
        fi  
done

while语句:

#!/bin/bash


echo "--"

VAR=10

while [ $VAR -eq 10 ]
do
        echo -n "please input which ranges is 15~20:"
        read key_value

        case $key_value in
                15|16|17|18|19|20)

                        if [ $key_value == 20 ];then
                                echo "i will skip the following command because continue"
                                continue
                        fi  

                        echo "输入的数据合法,$key_value"
                        ;;  
                "exit")
                        echo "goodbye"
                        break
                        ;;  
                *)  
                        echo "输入数据不合法"
                        ;;  
        esac

done

函数定义和引用

#!/bin/bash

echo "
shell 函数格式:
[ function ] funcname [()]
{
        action;

        [return ret;]
}
"

# 函数定义
function print_hello()
{
        echo "参数个数为:$#"
        if [ $# -ge 1 ];then
                echo "第一个参数为:$1"
                if [ $# -eq 2 ];then
                        echo "第二个参数为:"$2
                fi  
        else
                echo "$#"
        fi  

        echo "函数调用"
}

print_hello "para1" 3.131592653

函数参数

#!/bin/bash

echo "
\$# cli命令行输入的参数个数(脚本名字后面跟的参数的数量,参数之间以空格为分隔符)
\$* 所有的命令行参数
\$$ 本脚本运行时,操作系统分配的id号
\$! 后台运行的最后一个进程的id号
\$@$*相同,使用时是需要加引号,并在引号中返回每个参数
\$- 显示shell使用的当前选项,与set命令功能相同
\$? 显示最后一个命令的返回码。0表示最后一条命令($?往上紧挨着的那一条命令,不是指shell脚本的最后一行)没有出错,返回表示发生错误
"


echo "cli参数个数为: $#"
echo "本脚本的名字为: $0"
echo "第一个参数为: $1"
echo "第二个参数为: $2"


while :
do
        echo "\$#=$#"
        if [ $# == 0 ];then
                break
        fi  

        case $1 in
                "para1")
                        echo "i am para1"
                        shift
                        ;;  
    
                "para2")
                        echo "i am para2"
                        shift
                        ;;  
                *)  
                        shift
                        ;;  
        esac

done

文件读写

#!/bin/bash

echo "创建多级目录"
mkdir -p ./dir1/dir2/dir3

echo "删除子目录"
rm -rf ./dir1/dir2

echo "创建文件"
touch ./dir1/log.txt

echo "文件写入内容"
echo "hello world" > ./dir1/log.txt
echo "hello jacky" >> ./dir1/log.txt

echo "第一种磁盘文件内容读取方式"
while read line
do
        echo $line
done < ./dir1/log.txt

echo "第二种磁盘文件内容读取方式"
cat ./dir1/log.txt | while read line
do
        echo $line
done

通过ftp工具下载文件

ftp客户端执行,需要在目标Linux节点安装了ftp服务端,Yum或apt安装轻量级ftp服务器:vsftpd

#!/bin/bash

# 函数功能:         从ftp服务器读取文件
# 参数:           ip user pass fllename
function get_ftp_file() {
        local IP=$1
        local USER=$2
        local PASS=$3
        local FILE_NAME=$4

        # echo "闹钟信号到来,访问ftp服务器"
        ftp -v -n $IP<<EOF
        user $USER
        binary
        lcd ./
        prompt off
        get $FILE_NAME
        bye
        close
EOF
}
# 从ftp服务器下载版本文件
get_ftp_file 127.0.0.1 ftp 123456 file1;

curl工具的使用

(要求搭建好目标http服务器)

curl上传文件到http文件服务器:

curl http://47.131.65.28:80/upload -F "file=@./file.txt"

curl发json包

curl -X post http://47.131.65.28:80/upload? -d"{\"cmd type\":\"ping node\"}"

curl发json文件

curl -X post http://47.131.65.28:80/upload? -d@js_test.json

js_test.json文件内容:

{
	"cmd type":"get node list",
}

curl还可以发更为精准的Http包。掌握基础就行,如果以后你们工作需要用到时可自行百度。

wget文件下载

以下载redis源码为例:

[root@izwz93atpalb56zydy9bpyz tmp]# wget http://download.redis.io/releases/redis-2.6.14.tar.gz
--2020-01-07 15:44:15--  http://download.redis.io/releases/redis-2.6.14.tar.gz
Resolving download.redis.io (download.redis.io)... 109.74.203.151
Connecting to download.redis.io (download.redis.io)|109.74.203.151|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 995036 (972K) [application/x-gzip]
Saving to: ‘redis-2.6.14.tar.gz’

100%[========================================================================================================================>] 995,036     39.9KB/s   in 21s    

2020-01-07 15:44:37 (46.4 KB/s) - ‘redis-2.6.14.tar.gz’ saved [995036/995036]

[root@izwz93atpalb56zydy9bpyz tmp]# ll redis-2.6.14.tar.gz 
-rw-r--r--. 1 root root 995036 Oct  9  2013 redis-2.6.14.tar.gz
[root@izwz93atpalb56zydy9bpyz tmp]#

shell高级:游戏开发

显示一个实时时钟:

#!/bin/bash
#
# Author: LKJ
# Date: 2013/5/14
# Email: [email protected]
#

asciinumber=(
    '    .XEEEEb           ,:LHL          :LEEEEEG        .CNEEEEE8                bMNj       NHKKEEEEEX           1LEEE1    KEEEEEEEKNMHH       8EEEEEL.         cEEEEEO    '
    '   MEEEUXEEE8       jNEEEEE         EEEEHMEEEEU      EEEELLEEEEc             NEEEU      7EEEEEEEEEK        :EEEEEEN,    EEEEEEEEEEEEE     OEEEGC8EEEM      1EEELOLEEE3  '
    '  NEE.    OEEC      EY" MEE         OC      LEEc     :"      EEE            EEGEE3      8EN               MEEM.                  :EE.    1EEj     :EEO    1EE3     DEEc '
    ' ,EEj      EEE          HEE                  EEE             cEE:          EEU EEJ      NEC              EEE                     EEJ     EEE       EEE    EEN       KEE '
    ' HEE       jEE1         NEE                  EEE             EEE          EEM  EEJ      EE              LEE   ..                EEK      DEEj     :EE7   ,EE1       jEE '
    ' EEH        EEZ         KEE                 :EE1       .::jZEEG          EEU   EEJ     .EEEEEENC        EE77EEEEEEL            NEE        UEENj  bEE7    .EEX       :EE.'
    '.EEZ        EEM         KEE                 EEK        EEEEEEC         .EEc    EEC     :X3DGMEEEEU     3EEEED.".GEEE.         CEE.          EEEEEEE       EEEj     :EEE '
    ' EEZ        EEM         KEE               :EEK            "jNEEZ      :EE      EE7             MEEU    LEEb       EEE        .EE8         DEEL:.8EEEM      NEEENMEEEHEE '
    ' EEN       .EEG         KEE              bEEG                7EEM    jEEN738ODDEEM3b            EEE    MEE        8EE,       EEE         EEE      ,EEE      .bEEEEC XEE '
    ' LEE       3EE:         KEE            .EEE,                  EEE    LEEEEEEEEEEEEEE            XEE    8EE        cEE:      NEE         7EE1       jEE1            :EE: '
    ' .EEc      EEE          KEE           bEED                    EEE              EE1              EEE     EEX       EEE      3EE:         cEEc       7EEj           CEEG  '
    '  MEE7    NEE.          EEE         jEEK             C       EEE1              EEC     j      :EEE      CEEG     LEEj     .EEU           EEE:     .EEE          1EEEJ   '
    '   bEEEEEEEE.           EEE        NEEEEEEEEEEEE    bEEEEEEEEEE7               EEd    JEEEEEEEEEN        jEEEEEEEEE7     .EEE             KEEEEHEEEEL      8EEEEEEX     '
    '     DEEEL7             CGD        3GD3DOGGGGGUX     :DHEEEN8.                 bUd     7GNEEEMc            7LEEEX:       1XG                JHEEEM1        COLIN"       '
);

asciidot=(
    ' @@ '
    ' @@ '
);

len=${#asciinumber[@]};

#共有三个参数, 
#第一个是所要打印的数字, 
#第二个是之前打印的数字个数,
#第三个是之前打印的点的个数
function print_number {
    start=$(($1*17));
    start_y=$(($2*17+$3*4+$beg_y));

    for (( i = 0; i < len; i++ )); do
        echo -ne "\033[$((beg_x+i));${start_y}H\033[1;32m${asciinumber[$i]:$start:17}\033[0m";
    done
}

#print_dot有两个参数
#第一个参数是之前打印的数字个数
#第二个参数是之前打印的点的个数
function print_dot {
    local pt=$(($1*17+$2*4+beg_y));
    for (( j = 0; j < 2; j++ )); do
        echo -ne "\033[$((beg_x+j+3));${pt}H\033[1;32m${asciidot[$j]}\033[0m";
        echo -ne "\033[$((beg_x+j+10));${pt}H\033[1;32m${asciidot[$j]}\033[0m";
    done
}

function old_value {
    orows=`tput lines`; beg_x=$((orows/2-6));
    ocols=`tput cols`;  beg_y=$((ocols/2-54));

    ohur=$((10#`date +%H`));
    omin=$((10#`date +%M`));
    osec=$((10#`date +%S`));

    print_number $((ohur/10)) 0 0; print_number $((ohur%10)) 1 0;
    print_dot 2 0;
    print_number $((omin/10)) 2 1; print_number $((omin%10)) 3 1;
    print_dot 4 1;
    print_number $((osec/10)) 4 2; print_number $((osec%10)) 5 2;
}


function print_all {
    t_rows=`tput lines`; beg_x=$((t_rows/2-6));
    t_cols=`tput cols`;  beg_y=$((t_cols/2-54));

    if [[ $t_rows -ne $orows || $t_cols -ne $ocols ]]; then
        orows=$t_rows;
        ocols=$t_cols;
        check_win $orows $ocols;
        old_value;
    fi

    hur=$((10#`date +%H`));
    hft=$((hur/10)); hsd=$((hur%10));
    if [[ $ohft -ne $hft ]]; then
        print_number $hft 0 0;
        ohft=$hft;
    fi
    if [[ $ohsd -ne $hsd ]]; then
        print_number $hsd 1 0;
        ohsd=$hsd;
    fi

    min=$((10#`date +%M`));
    mft=$((min/10)); msd=$((min%10));
    if [[ $omft -ne $mft ]]; then
        print_number $mft 2 1;
        omft=$mft;
    fi
    if [[ $omsd -ne $msd ]]; then
        print_number $msd 3 1;
        omsd=$msd;
    fi

    sec=$((10#`date +%S`)); #出现(())bug的原因:date +%S < 10 的时候会有前置0
                    #所以((08/10))会出错,但是使用expr不会出现错误,let也会有此错误
                    #解决方法是$((10#08/10));
    sft=$((sec/10)); ssd=$((sec%10));
    if [[ $osft -ne $sft ]]; then
        print_number $sft 4 2;
        osft=$sft;
    fi
    if [[ $ossd -ne $ssd ]]; then
        print_number $ssd 5 2;
        ossd=$ssd;
    fi

}

function check_win {
    if [[ $1 -lt 14 || $2 -lt 110 ]]; then
        clear;
        echo -ne "\033[8;15;120t"; #change the window size
    fi
    clear; #若窗口改变则重新刷新
}


function INIT {
    tput smcup; #保存屏幕
    check_win `tput lines` `tput cols`;
    trap 'EXIT;' SIGINT; #将光标重新设置为白色
    tput civis; #设置光标不可见
    old_value;
}

function EXIT {
    tput cvvis; #使光标可见
    tput rmcup; #恢复屏幕
    exit 0;
}


INIT;
while true; do
    read -t 1 -n 1 anykey;
    if [[ $? -eq 0 ]]; then
        EXIT;
    fi
    print_all;
#    if sleep 0.3 &> /dev/null; then
#       sleep 1; 
#    fi;
done

exit 0;

贪吃蛇

#!/bin/bash

# filename: snake.sh
# snake game
# Author: LKJ 2013.5.17

good_game=(
    '                                                 '
    '                G A M E  O V E R !               '
    '                                                 '
    '                   Score:                        '
    '          press   q   to quit                    '
    '          press   n   to start a new game        '
    '          press   s   to change the speed        '
    '                                                 '
);

game_start=(
    '                                                 '
    '                ~~~ S N A K E ~~~                '
    '                                                 '
    '                  Author:  LKJ                   '
    '         space or enter   pause/play             '
    '         q                quit at any time       '
    '         s                change the speed       '
    '                                                 '
    '         Press <Enter> to start the game         '
    '                                                 '
);

snake_exit() {  #退出游戏
    stty echo;  #恢复回显
    tput rmcup; #恢复屏幕
    tput cvvis; #恢复光标
    exit 0;
}

draw_gui() {                                  # 画边框 
    clear;
    color="\033[34m*\033[0m";
    for (( i = 0; i < $1; i++ )); do
        echo -ne "\033[$i;0H${color}";
        echo -ne "\033[$i;$2H${color}";
    done

    for (( i = 0; i <= $2; i++ )); do
        echo -ne "\033[0;${i}H${color}";
        echo -ne "\033[$1;${i}H${color}";
    done

    ch_speed 0;
    echo -ne "\033[$Lines;$((yscore-10))H\033[36mScores: 0\033[0m";
    echo -en "\033[$Lines;$((Cols-50))H\033[33mPress <space> or enter to pause game\033[0m";
}

snake_init() {
    Lines=`tput lines`; Cols=`tput cols`;     #得到屏幕的长宽
    xline=$((Lines/2)); ycols=4;              #开始的位置
    xscore=$Lines;      yscore=$((Cols/2));   #打印分数的位置
    xcent=$xline;       ycent=$yscore;        #中心点位置
    xrand=0;            yrand=0;              #随机点 
    sumscore=0;         liveflag=1;           #总分和点存在标记
    sumnode=0;          foodscore=0;          #总共要加长的节点和点的分数

    snake="0000 ";                            #初始化贪吃蛇
    pos=(right right right right right);      #开始节点的方向
    xpt=($xline $xline $xline $xline $xline); #开始的各个节点的x坐标
    ypt=(5 4 3 2 1);                          #开始的各个节点的y坐标
    speed=(0.05 0.1 0.15);  spk=${spk:-1};    #速度 默认速度

    draw_gui $((Lines-1)) $Cols
}

game_pause() {                                #暂定游戏
    echo -en "\033[$Lines;$((Cols-50))H\033[33mGame paused, Use space or enter key to continue\033[0m";
    while read -n 1 space; do
        [[ ${space:-enter} = enter ]] && \
            echo -en "\033[$Lines;$((Cols-50))H\033[33mPress <space> or enter to pause game           \033[0m" && return;
        [[ ${space:-enter} = q ]] && snake_exit;
    done
}

# $1 节点位置 
update() {                                    #更新各个节点坐标
    case ${pos[$1]} in
        right) ((ypt[$1]++));;
         left) ((ypt[$1]--));;
         down) ((xpt[$1]++));;
           up) ((xpt[$1]--));;
    esac
}

ch_speed() {                                  #更新速度
     [[ $# -eq 0 ]] && spk=$(((spk+1)%3));
     case $spk in
         0) temp="Fast  ";;
         1) temp="Medium";;
         2) temp="Slow  ";;
     esac
     echo -ne "\033[$Lines;3H\033[33mSpeed: $temp\033[0m";
}

Gooooo() {                                   #更新方向
    case ${key:-enter} in
        j|J) [[ ${pos[0]} != "up"    ]] && pos[0]="down";;
        k|K) [[ ${pos[0]} != "down"  ]] && pos[0]="up";;
        h|H) [[ ${pos[0]} != "right" ]] && pos[0]="left";;
        l|L) [[ ${pos[0]} != "left"  ]] && pos[0]="right";;
        s|S) ch_speed;;
        q|Q) snake_exit;;
      enter) game_pause;;
    esac
}

add_node() {                                 #增加节点
    snake="0$snake";
    pos=(${pos[0]} ${pos[@]});
    xpt=(${xpt[0]} ${xpt[@]});
    ypt=(${ypt[0]} ${ypt[@]});
    update 0;

    local x=${xpt[0]} y=${ypt[0]}
    (( ((x>=$((Lines-1)))) || ((x<=1)) || ((y>=Cols)) || ((y<=1)) )) && return 1; #撞墙

    for (( i = $((${#snake}-1)); i > 0; i-- )); do
        (( ${xpt[0]} == ${xpt[$i]} && ${ypt[0]} == ${ypt[$i]} )) && return 1; #crashed
    done

    echo -ne "\033[${xpt[0]};${ypt[0]}H\033[32m${snake[@]:0:1}\033[0m";
    return 0;
}

mk_random() {                               #产生随机点和随机数
    xrand=$((RANDOM%(Lines-3)+2));
    yrand=$((RANDOM%(Cols-2)+2));
    foodscore=$((RANDOM%9+1));

    echo -ne "\033[$xrand;${yrand}H$foodscore";
    liveflag=0;
}


new_game() {                                #重新开始新游戏
    snake_init;
    while true; do
        read -t ${speed[$spk]} -n 1 key;
        [[ $? -eq 0 ]] && Gooooo;

        ((liveflag==0)) || mk_random;
        if (( sumnode > 0 )); then
            ((sumnode--));
            add_node; (($?==0)) || return 1;
        else
            update 0;
            echo -ne "\033[${xpt[0]};${ypt[0]}H\033[32m${snake[@]:0:1}\033[0m";

            for (( i = $((${#snake}-1)); i > 0; i-- )); do
                update $i;
                echo -ne "\033[${xpt[$i]};${ypt[$i]}H\033[32m${snake[@]:$i:1}\033[0m";

                (( ${xpt[0]} == ${xpt[$i]} && ${ypt[0]} == ${ypt[$i]} )) && return 1; #crashed
                [[ ${pos[$((i-1))]} = ${pos[$i]} ]] || pos[$i]=${pos[$((i-1))]};
            done
        fi

        local x=${xpt[0]} y=${ypt[0]}
        (( ((x>=$((Lines-1)))) || ((x<=1)) || ((y>=Cols)) || ((y<=1)) )) && return 1; #撞墙

        (( x==xrand && y==yrand )) && ((liveflag=1)) && ((sumnode+=foodscore)) && ((sumscore+=foodscore));

        echo -ne "\033[$xscore;$((yscore-2))H$sumscore";
    done
}

print_good_game() {
    local x=$((xcent-4)) y=$((ycent-25))
    for (( i = 0; i < 8; i++ )); do
        echo -ne "\033[$((x+i));${y}H\033[45m${good_game[$i]}\033[0m";
    done
    echo -ne "\033[$((x+3));$((ycent+1))H\033[45m${sumscore}\033[0m";
}


print_game_start() {
    snake_init;

    local x=$((xcent-5)) y=$((ycent-25))
    for (( i = 0; i < 10; i++ )); do
        echo -ne "\033[$((x+i));${y}H\033[45m${game_start[$i]}\033[0m";
    done

    while read -n 1 anykey; do
        [[ ${anykey:-enter} = enter ]] && break;
        [[ ${anykey:-enter} = q ]] && snake_exit;
        [[ ${anykey:-enter} = s ]] && ch_speed;
    done

    while true; do
        new_game;
        print_good_game;
        while read -n 1 anykey; do
            [[ $anykey = n ]] && break;
            [[ $anykey = q ]] && snake_exit;
        done
    done
}

game_main() {
    trap 'snake_exit;' SIGTERM SIGINT;
    stty -echo;                               #取消回显
    tput civis;                               #隐藏光标
    tput smcup; clear;                        #保存屏幕并清屏

    print_game_start;                         #开始游戏 
}

game_main;

结语:关于shell编程,还要更多的内容,不过上面的知识基本就足够你用到离开程序员这个行业了。我在工作中,遇到的大多数程序员,或者说叫码农(算不上真正的程序员的那种),他们的shell编程能力之弱,让人大跌眼镜。把shell程序写的能像博主这种水平的,目前尚未遇到过(可能没有去过大公司吧)。

上述内容是笔者之前讲过一个shell编程的视频课程的部分内容,发布在csdn上面,不过目前已经下架。

下节课开始,我们讲基于c/c++的 《linux 应用开发》。

由于本课程讲的是真实场景,博客内容全部以笔者的从业经历来编写,由浅入深,由点到面,逐步提升。
本课程计划讲18篇左右。
如果是想转型到linux应用开发,本专栏对你会有很多的帮助。能迅速带你从门外,到入门,再到中级的水准。水平的提高对应的,薪资会以k为最小单位去提升。
写一篇博客,排版内容需要花费1~2小时。后续课程需要订阅才能阅读全部内容。

《linux应用开发》技术交流qq群:528671785

在这里插入图片描述

发布了61 篇原创文章 · 获赞 63 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/jacky128256/article/details/103871447