SHELL腳本編程基礎知識

         SHELL腳本編程基礎知識php

                       做者:尹正傑node

版權聲明:原創做品,謝絕轉載!不然將追究法律責任。python

 

  Linux之父Linus有一句話很經典:"Talk is cheap, show me the code",雖然咱們是一枚小小的運維工程師,但工做中確實是有一些任務是須要寫成腳本方式來實現的。在招聘面試過程當中,要求運維人員會shell編程是必須的,甚至有的公司得要求運維會Java,Python或者Golang。mysql

 

一.編程基礎概念
1>.程序相關概念
程序:
  算法+數據結構
數據:
  是程序的核心
數據結構:
  數據在計算機中的類型和組織方式
算法:
  處理數據的方式
2>.程序編程風格
過程式:
  以指令爲中心,數據服務於指令
對象式:
  以數據爲中心,指令服務於數據
3>.shell程序
  shell是一個命令解釋器,它在操做系統的最外層,負責直接與用戶對話,把用戶的輸入解釋給操做系統,並處理各類各樣的操做系統的輸出結果,輸出到屏幕返回給用戶。
 
  這種對話方式能夠是交互的方式(從鍵盤輸入命令,能夠當即獲得shell的迴應),或非交互(腳本)的方式。換句話說,Shell是一個命令行解釋器,它爲用戶提供一個像Linux內核發送請求以便運行程序的界面系統級程序,用戶能夠用Shell來啓動,掛起,中止甚至是編寫一些程序。
 
  Shell仍是一個功能至關強的編程語言,易編寫,易調試,靈活性較強。Shell是解釋執行的腳本語言,在Shell中能夠直接調用Linux系統命令。
 
二.程序的執行方式
1>.計算機
  計算機只能識別二進制,所以運行程序其實就是運行二進制指令。
2>.編程語言
  編程語言是人與計算機之間交互的語言。
3>.低級編程語言
機器語言:
  二進制的0和1的序列,稱爲機器指令。與天然語言差別太大,難懂、難寫。
彙編:
  用一些助記符號替代機器指令,稱爲彙編語言。     如:ADD A,B 將寄存器A的數與寄存器B的數相加獲得的數放到寄存器A中       彙編語言寫好的程序須要彙編程序轉換成機器指令       彙編語言稍微好理解,即機器指令對應的助記符,助記符更接近天然語言
4>.高級編程語言
編譯型語言執行過程:高級語言-->編譯器-->機器代碼-->執行
  典型表明:C,C++,等
  特色:開發效率低,執行效率高 解釋型語言執行過程:高級語言
-->執行-->解釋器-->機器代碼   典型表明:shell,python,php,JavaScript,perl,Scala等
  特色:開發效率高,執行效率低
5>.編譯和解釋型語言
不管是編譯型語言仍是解釋型語言在外面生活中都能找到相似吃菜的場景:
  炒菜:
    上菜後就直接可用吃啦,可是咱們得等炒菜的過程,這就很像咱們計算機編譯型語言的執行過程。

  火鍋:
    不須要提早把菜弄熟,而是想吃哪一個就先煮哪一個菜,邊吃火鍋裏煮着,這就很像咱們計算機解釋性語言的執行過程。

6>.編程邏輯處理方式linux

順序執行
循環執行
選擇執行
7>.shell編程(過程式、解釋執行)
編程語言的基本結構:
  各類系統命令的組合
  數據存儲:變量、數組
  表達式:如:"a + b"   語句:if...else,case,while do...done等
 
三.腳本語言的種類 
1>.php語言
  它是網頁程序,也是腳本語言,更專一於web頁面的開發,例如:dedecms,discuz。也能夠處理系統日誌,配置文件等。
2>. perl語言
  perl腳本語言,比shell強大的多,2010年前很火,語法靈活,複雜,實現方法不少,不易讀,團隊協做困難。
3>. Python語言
  近幾年很火的語言,能夠作腳本開發,也能夠實現web開發(但並非和作電商網站開發喲~),中等以上的公司都要求會python。
4>.shell語言
  最容易上手的腳本,shell的優點在於處理操做系統底層的業務,由於有大量的系統命令爲它作支撐,2000多個命令都是shell編程的有力支撐,特別是grep,awk,sed等。例如:一件軟件安裝,優化,監控報警腳本,常規的業務應用,shell開發更簡單快速。如下是解釋器支持的shell類型。
Golang語言,一個很適合作自動化運維的編程語言
    它是編譯型語言,Go語言專門針對多處理器系統應用程序的編程進行了優化,使用Go編譯的程序能夠媲美C或C++代碼的速度,並且更加安全、支持並行進程,很適合作系統開發或自動化運維。
Golang語言,一個很適合作自動化運維的編程語言

 

.Linux支持的Shell
Bourne Shell:
  從1979年起Unix就開始使用Bourne Shell,Bourne Shell的主文件名爲sh,咱們最多見的"/bin/sh"。

C Shell:
  C Shell主要在BSD版的Unix系統中使用,其語法和C語言相相似而得名,咱們最多見的就是「/bin/csh」。

Bash:
  Bash與sh兼容,如今使用的Linux就是使用Bash做爲用戶的基本Shell。
  
舒適提示:
  Shell的兩種主要語法類型有Bourne和C,這兩種語法彼此不兼容。
  Bourne家族主要包括sh,ksh,Bash,psh,zsh;
  C家族主要包括:csh,tcsh。

經常使用操做系統的默認Shell
  linux操做系統:
    Bourne Again shell(bash)。
  Solaris和FreeBSD操做系統:
    Bourne shell(sh)。
  AIX操做系統:
    Korn Shell(ksh)。
  HP-UX操做系統:
    POSIX shell(sh)。
  Centos linux操做系統:
    默認的shell是bash。
[root@node101.yinzhengjie.org.cn ~]# cat /etc/shells     #這個文件保存着當前操做系統支持的Shell版本
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
[root@node101.yinzhengjie.org.cn ~]# 
[root@node101.yinzhengjie.org.cn ~]# cat /etc/shells     #該文件保存着當前操做系統支持的Shell版本

 

五.shell腳本基礎
1>.什麼是shelll腳本
  包含一些命令或聲明,並符合必定格式的文本文件
2>.格式要求
編寫腳本的時候咱們一般會在第一行指定當前腳本所用的解釋權,咱們稱之爲"首行shebang機制",這裏的"shebang"其實就是"#!"的讀音翻譯。

咱們寫腳本的時候通常都是調用bash,因此第一行咱們要寫上這麼一行:"#!/bin/bash",要注意的是這可不是註釋行喲~而是告訴內核咱們用的是哪一種解釋器,下面的全部行,若是在出現相似的內核都會認爲是註釋行。

常看法釋性語言的開頭標識內容以下:
  #!/bin/bash
  #!/bin/sh
  #!/usr/bin/awk
  #!/bin/sed
  #!/usr/bin/tcl
  #!/usr/bin/expect
  #!/usr/bin/perl
  #!/usr/bin/env python
 
3>.shell腳本的用途
自動化經常使用命令

執行系統管理和故障排除
建立簡單的應用程序
處理文本或文件

 

六.建立第一個shell腳本
1>.使用文本編輯器來建立文本文件
[root@node101.yinzhengjie.org.cn ~]# mkdir -pv /data/script
mkdir: created directory ‘/data’
mkdir: created directory ‘/data/script’
[root@node101.yinzhengjie.org.cn ~]# 
[root@node101.yinzhengjie.org.cn ~]# cd /data/script/
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# vim hello.sh
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# cat hello.sh
#!/bin/bash
#@author:yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie
#Description: This is the first script

echo "hello world"
echo "My hostname is `hostname`"
[root@node101.yinzhengjie.org.cn /data/script]# 
2>.運行腳本
[root@node101.yinzhengjie.org.cn /data/script]# ll
total 4
-rw-r--r-- 1 root root 166 Nov 12 23:26 hello.sh
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# chmod +x hello.sh      #給予執行權限,在命令行上指定腳本的絕對或相對路徑
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# ll
total 4
-rwxr-xr-x 1 root root 166 Nov 12 23:26 hello.sh
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# ./hello.sh           #有執行權限後,可用用相對路徑調用執行   
hello world
My hostname is node101.yinzhengjie.org.cn
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# /data/script/hello.sh   #有執行權限後,也能夠直接使用絕對路徑調用執行
hello world
My hostname is node101.yinzhengjie.org.cn
[root@node101.yinzhengjie.org.cn /data/script]# 
給予執行權限,在命令行上指定腳本的絕對或相對路徑
[root@node101.yinzhengjie.org.cn /data/script]# ll
total 4
-rwxr-xr-x 1 root root 166 Nov 12 23:26 hello.sh
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# chmod -x hello.sh 
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# ll
total 4
-rw-r--r-- 1 root root 166 Nov 12 23:26 hello.sh
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# sh hello.sh                #直接運行解釋器,將腳本做爲解釋器程序的參數運行
hello world
My hostname is node101.yinzhengjie.org.cn
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# 
直接運行解釋器,將腳本做爲解釋器程序的參數運行
[root@node101.yinzhengjie.org.cn /data/script]# echo $PATH
/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/home/softwares/jdk1.8.0_201/bin:/root/bin:/home/softwares/mysql/bin/
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# vim /etc/profile.d/env.sh
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# cat /etc/profile.d/env.sh
PATH=/data/script:$PATH
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# source /etc/profile.d/env.sh
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# echo $PATH
/data/script:/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/home/softwares/jdk1.8.0_201/bin:/root/bin:/home/softwares/mysql/bin/
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# he
head     help     hexdump  
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# ll
total 4
-rw-r--r-- 1 root root 166 Nov 12 23:26 hello.sh
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# chmod +x hello.sh 
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# ll
total 4
-rwxr-xr-x 1 root root 166 Nov 12 23:26 hello.sh
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# cd
[root@node101.yinzhengjie.org.cn ~]# 
[root@node101.yinzhengjie.org.cn ~]# hello.sh 
hello world
My hostname is node101.yinzhengjie.org.cn
[root@node101.yinzhengjie.org.cn ~]# 
[root@node101.yinzhengjie.org.cn ~]# 
將編寫腳本路徑添加到"$PATH"環境變量中,只要有執行權限的腳本文件都可被調用執行
3>. "source"和"."還有"sh"調用腳本的區別
  "source""."的功能是同樣的,能夠調用腳本,並將腳本里的函數也傳遞到當前的腳本或者解釋器中,即不會開啓新的bash而是在當前bash中運行。

  "sh"後面跟腳本名稱,則不會將該腳本的函數傳遞進來,即須要開啓新的bash,"sh"其實是執行一個腳本,最後執行完畢會將內存釋放掉,不會保存變量。

  而".""source"則不會新的bash進程,這就是爲何在/etc/init.d/這個目錄下有不少的腳本都會用"."去調用腳本。

  綜上所述:
    生產環境中編寫腳本通常會使用sh命令去執行腳本,由於使用sh命令執行的腳本執行完畢後會自動釋放內存並不會影響當前進程中的變量。除非你明確直到想要腳本中的變量要在當前bash中生效(好比從新讀取配置文件)則可使用"source"或者"."
[root@node101.yinzhengjie.org.cn ~]# vim shell/test.sh
[root@node101.yinzhengjie.org.cn ~]# 
[root@node101.yinzhengjie.org.cn ~]# cat shell/test.sh
#!/bin/bash
#
#********************************************************************
#Author:        yinzhengjie
#QQ:             1053419035
#Date:             2019-11-21
#FileName:        test.sh
#URL:             http://www.cnblogs.com/yinzhengjie
#Description:        The test script
#Copyright notice:     original works, no reprint! Otherwise, legal liability will be investigated.
#********************************************************************

NAME="尹正傑"           #定義一個變量
echo $NAME              #打印我們的上面定義的變量對應的值
echo $BASHPID           #打印當前腳本執行的bash的pid編號
sleep 60                #讓腳本晚一點結束,我們可使用pstree命令觀察是否有新的進程生成。
[root@node101.yinzhengjie.org.cn ~]# 
[root@node101.yinzhengjie.org.cn ~]# NAME="Jason Yin"
[root@node101.yinzhengjie.org.cn ~]# echo $NAME         #這是打印的在當前shell中定義的變量
Jason Yin
[root@node101.yinzhengjie.org.cn ~]# 
[root@node101.yinzhengjie.org.cn ~]# bash shell/test.sh     #不難發現咱們在腳本中定義了變量被打印出來了,
尹正傑
14813
[root@node101.yinzhengjie.org.cn ~]# echo $NAME          #咱們發現執行完腳本後當前shell的變量並無被覆蓋喲~
Jason Yin
[root@node101.yinzhengjie.org.cn ~]#


上面代碼進入阻塞狀態時,我們開啓新的終端執行以下命令:
[root@node101.yinzhengjie.org.cn ~]# pstree -p | grep sshd    #能夠看到有一個sleep進程id爲14814,而其父進程則爲14813喲,結合上面的輸出進行對比。
           |-sshd(3403)-+-sshd(3819)---bash(3824)-+-grep(14816)
           |            `-sshd(4677)---bash(4679)---bash(14813)---sleep(14814)
[root@node101.yinzhengjie.org.cn ~]# 
sh案例(在CentOS操做系統sh其實就是bash命令的軟鏈接)
[root@node101.yinzhengjie.org.cn ~]# vim shell/test.sh
[root@node101.yinzhengjie.org.cn ~]# 
[root@node101.yinzhengjie.org.cn ~]# cat shell/test.sh
#!/bin/bash
#
#********************************************************************
#Author:        yinzhengjie
#QQ:             1053419035
#Date:             2019-11-21
#FileName:        test.sh
#URL:             http://www.cnblogs.com/yinzhengjie
#Description:        The test script
#Copyright notice:     original works, no reprint! Otherwise, legal liability will be investigated.
#********************************************************************

NAME="尹正傑"           #定義一個變量
echo $NAME              #打印我們的上面定義的變量對應的值
echo $BASHPID           #打印當前腳本執行的bash的pid編號
sleep 60                #讓腳本晚一點結束,我們可使用pstree命令觀察是否有新的進程生成。
[root@node101.yinzhengjie.org.cn ~]# 
[root@node101.yinzhengjie.org.cn ~]# NAME="Jason Yin"
[root@node101.yinzhengjie.org.cn ~]# echo $NAME
Jason Yin
[root@node101.yinzhengjie.org.cn ~]# [root@node101.yinzhengjie.org.cn ~]# chmod +x shell/test.sh 
[root@node101.yinzhengjie.org.cn ~]# 
[root@node101.yinzhengjie.org.cn ~]# ./shell/test.sh 
尹正傑
14830


當執行上面的腳本進入阻塞狀態時,在其它終端執行如下命令(發現有執行權限的腳本在執行代碼時也開啓了新的bash):
[root@node101.yinzhengjie.org.cn ~]# pstree -p | grep sshd
           |-sshd(3403)-+-sshd(3819)---bash(3824)-+-grep(14833)
           |            `-sshd(4677)---bash(4679)---test.sh(14830)---sleep(14831)
[root@node101.yinzhengjie.org.cn ~]# 
發現有執行權限的腳本在執行代碼時也開啓了新的bash
 
七.腳本規範

1>.腳本開頭約定web

開頭執行腳本解釋器

開頭加版權等信息

腳本中儘可能不要用中文註釋,儘可能用英文註釋,防止本機或切換系統環境後中文亂碼的困擾
腳本以".sh"爲擴展名

代碼書寫優秀習慣
    (1).成對內容的一次寫出來,防止漏寫。例如:{},[],'',``,"".
    (2).[]中括號兩端都要有空格,書寫時便可流出空格[ ],而後退格書寫內容。
    (3).流程控制語句一次書寫完,在添加內容。

2>.腳本的基本結構面試

#!SHEBANG
CONFIGURATION_VARIABLES
FUNCTION_DEFINITIONS
MAIN_CODE

3>.shell腳本示例算法

[root@node101.yinzhengjie.org.cn /data/script]# cat hello.sh 
#!/bin/bash
#
#********************************************************************
#Author:        yinzhengjie
#Email:         y1053419035@qq.com
#Date:             2019-11-13
#FileName:        hello.sh
#URL:             http://www.cnblogs.com/yinzhengjie
#Description:        This is the first script
#Copyright (C):     Original works, no reprint! Otherwise, legal liability will be investigated
#********************************************************************

echo -e "hello world"
echo -e "My hostname is `hostname`"
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# 

4>.可配置vim編輯文件時自動加上以上信息版權信息(方法是修改~/.vimrc配置文件)sql

[root@node101.yinzhengjie.org.cn ~]# cat ~/.vimrc         #下面附有對每行的中文解釋,使用時須要將這些中文字符提早刪除喲~
set ignorecase        #忽略大小寫
set cursorline        #移動光標時添加下劃線
set autoindent        #自動進行行對其
autocmd BufNewFile *.sh exec ":call SetTitle()"        #當打開是以"*.sh"的文件名稱時,就會自動調用下面咱們定義的函數啦!

func SetTitle()
    if expand("%:e") == 'sh'
    call setline(1,"#!/bin/bash") 
    call setline(2,"#") 
    call setline(3,"#********************************************************************") 
    call setline(4,"#Author:        yinzhengjie") 
    call setline(5,"#QQ:             1053419035") 
    call setline(6,"#Date:             ".strftime("%Y-%m-%d"))
    call setline(7,"#FileName:        ".expand("%"))
    call setline(8,"#URL:             http://www.cnblogs.com/yinzhengjie")
    call setline(9,"#Description:        The test script") 
    call setline(10,"#Copyright (C):     ".strftime("%Y")." All rights reserved")
    call setline(11,"#********************************************************************") 
    call setline(12,"") 
    endif
endfunc
autocmd BufNewFile * normal G
[root@node101.yinzhengjie.org.cn ~]# 
 
八.腳本調試
1>.檢測腳本中的語法錯誤
[root@node101.yinzhengjie.org.cn /data/script]# ll
total 4
-rwxr-xr-x 1 root root 166 Nov 12 23:26 hello.sh
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# bash -n hello.sh     #檢查腳本的語法並不會執行,如有語法錯誤就會拋出異常,只能檢查語法錯誤不能檢查命令是否錯誤。
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# bash  hello.sh       #調用腳本並執行
hello world
My hostname is node101.yinzhengjie.org.cn
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# bash -n hello.sh   #檢查腳本的語法並不會執行
2>.調試執行
[root@node101.yinzhengjie.org.cn /data/script]# bash -x  hello.sh     #查看腳本的執行過程,尤爲時表明出錯時,可用看到是具體哪行代碼出錯啦~
+ echo 'hello world'
hello world
++ hostname
+ echo 'My hostname is node101.yinzhengjie.org.cn'
My hostname is node101.yinzhengjie.org.cn
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# 
[root@node101.yinzhengjie.org.cn /data/script]# bash -x hello.sh     #查看腳本的執行過程

 

九. bash漏洞修復方式
1>.背景
  網絡安全專家警告稱,開源軟件Linux中一個頻繁使用的片斷「Bash」,發現存在安全漏洞,其對計算機用戶形成的威脅可能要超過2014年4月爆出的"心臟出血"(Heartbleed)漏洞。
2>.漏洞原理
  bash是用於控制Linux計算機命令提示符的軟件。網絡安全專家表示,黑客能夠利用Bash中的一個安全漏洞,對目標計算機系統進行徹底控制。

  網絡安全公司Trail of Bits的首席執行官丹·吉多(Dan Guido)指出:與"Heartbleed"相比,後者只容許黑客窺探計算機,但不會讓黑客得到計算機的控制權。"他說:"利用Bash漏洞的方法也簡單得多,你能夠直接剪切和粘貼一行軟件代碼,就能取得很好的效果。"吉多還表示,他正考慮將本身公司非必要的服務器斷網,以保護他們不會受到Bash漏洞的攻擊,直到他可以修補這一漏洞爲止。

  網絡安全公司Rapid7的工程經理託德·比爾茲利(Tod Beardsley)則警告稱,Bash漏洞的嚴重程度被評爲10級,意味着它具備最大的影響力,而其利用的難度被評爲"低"級,意味着黑客比較容易地利用其發動網絡攻擊。

  比爾茲利稱:"利用這個漏洞,攻擊者可能會接管計算機的整個操做系統,得以訪問機密信息,並對系統進行更改等等。任何人的計算機系統,若是使用了Bash軟件,都須要當即打上補丁。"
3>.修復方式
centos系統(若是是centos系統只要運行下面簡單的命令就能夠)
  yum clean all
  yum makecache
  yum -y update bash

Ubuntu系統
  apt-get update
  apt-get -y install --only-upgrade bash

Debian系統
  若是是7.5 64位 && 32位環境運行
  apt-get update
  apt-get -y install --only-upgrade bash

 

十.擴展小知識:Shell編寫的俄羅斯方塊。
#!/bin/bash
 
 
#APP declaration
APP_NAME="${0##*[\\/]}"
APP_VERSION="1.0"
 
 
#顏色定義
cRed=1
cGreen=2
cYellow=3
cBlue=4
cFuchsia=5
cCyan=6
cWhite=7
colorTable=($cRed $cGreen $cYellow $cBlue $cFuchsia $cCyan $cWhite)
 
#位置和大小
iLeft=3
iTop=2
((iTrayLeft = iLeft + 2))
((iTrayTop = iTop + 1))
((iTrayWidth = 10))
((iTrayHeight = 15))
 
#顏色設置
cBorder=$cGreen
cScore=$cFuchsia
cScoreValue=$cCyan
 
#控制信號
#改遊戲使用兩個進程,一個用於接收輸入,一個用於遊戲流程和顯示界面;
#當前者接收到上下左右等按鍵時,經過向後者發送signal的方式通知後者。
sigRotate=25
sigLeft=26
sigRight=27
sigDown=28
sigAllDown=29
sigExit=30
 
#七中不一樣的方塊的定義
#經過旋轉,每種方塊的顯示的樣式可能有幾種
box0=(0 0 0 1 1 0 1 1)
box1=(0 2 1 2 2 2 3 2 1 0 1 1 1 2 1 3)
box2=(0 0 0 1 1 1 1 2 0 1 1 0 1 1 2 0)
box3=(0 1 0 2 1 0 1 1 0 0 1 0 1 1 2 1)
box4=(0 1 0 2 1 1 2 1 1 0 1 1 1 2 2 2 0 1 1 1 2 0 2 1 0 0 1 0 1 1 1 2)
box5=(0 1 1 1 2 1 2 2 1 0 1 1 1 2 2 0 0 0 0 1 1 1 2 1 0 2 1 0 1 1 1 2)
box6=(0 1 1 1 1 2 2 1 1 0 1 1 1 2 2 1 0 1 1 0 1 1 2 1 0 1 1 0 1 1 1 2)
#全部其中方塊的定義都放到box變量中
box=(${box0[@]} ${box1[@]} ${box2[@]} ${box3[@]} ${box4[@]} ${box5[@]} ${box6[@]})
#各類方塊旋轉後可能的樣式數目
countBox=(1 2 2 2 4 4 4)
#各類方塊再box數組中的偏移
offsetBox=(0 1 3 5 7 11 15)
 
#每提升一個速度級須要積累的分數
iScoreEachLevel=50        #be greater than 7
 
#運行時數據
sig=0                #接收到的signal
iScore=0        #總分
iLevel=0        #速度級
boxNew=()        #新下落的方塊的位置定義
cBoxNew=0        #新下落的方塊的顏色
iBoxNewType=0        #新下落的方塊的種類
iBoxNewRotate=0        #新下落的方塊的旋轉角度
boxCur=()        #當前方塊的位置定義
cBoxCur=0        #當前方塊的顏色
iBoxCurType=0        #當前方塊的種類
iBoxCurRotate=0        #當前方塊的旋轉角度
boxCurX=-1        #當前方塊的x座標位置
boxCurY=-1        #當前方塊的y座標位置
iMap=()                #背景方塊圖表
 
#初始化全部背景方塊爲-1, 表示沒有方塊
for ((i = 0; i < iTrayHeight * iTrayWidth; i++)); do iMap[$i]=-1; done
 
 
#接收輸入的進程的主函數
function RunAsKeyReceiver()
{
        local pidDisplayer key aKey sig cESC sTTY
 
        pidDisplayer=$1
        aKey=(0 0 0)
 
        cESC=`echo -ne "\033"`
        cSpace=`echo -ne "\040"`
 
        #保存終端屬性。在read -s讀取終端鍵時,終端的屬性會被暫時改變。
        #若是在read -s時程序被不幸殺掉,可能會致使終端混亂,
        #須要在程序退出時恢復終端屬性。
        sTTY=`stty -g`
 
        #捕捉退出信號
        trap "MyExit;" INT TERM
        trap "MyExitNoSub;" $sigExit
 
        #隱藏光標
        echo -ne "\033[?25l"
 
 
        while :
        do
                #讀取輸入。注-s不回顯,-n讀到一個字符當即返回
                read -s -n 1 key
 
                aKey[0]=${aKey[1]}
                aKey[1]=${aKey[2]}
                aKey[2]=$key
                sig=0
 
                #判斷輸入了何種鍵
                if [[ $key == $cESC && ${aKey[1]} == $cESC ]]
                then
                        #ESC鍵
                        MyExit
                elif [[ ${aKey[0]} == $cESC && ${aKey[1]} == "[" ]]
                then
                        if [[ $key == "A" ]]; then sig=$sigRotate        #<向上鍵>
                        elif [[ $key == "B" ]]; then sig=$sigDown        #<向下鍵>
                        elif [[ $key == "D" ]]; then sig=$sigLeft        #<向左鍵>
                        elif [[ $key == "C" ]]; then sig=$sigRight        #<向右鍵>
                        fi
                elif [[ $key == "W" || $key == "w" ]]; then sig=$sigRotate        #W, w
                elif [[ $key == "S" || $key == "s" ]]; then sig=$sigDown        #S, s
                elif [[ $key == "A" || $key == "a" ]]; then sig=$sigLeft        #A, a
                elif [[ $key == "D" || $key == "d" ]]; then sig=$sigRight        #D, d
                elif [[ "[$key]" == "[]" ]]; then sig=$sigAllDown        #空格鍵
                elif [[ $key == "Q" || $key == "q" ]]                        #Q, q
                then
                        MyExit
                fi
 
                if [[ $sig != 0 ]]
                then
                        #向另外一進程發送消息
                        kill -$sig $pidDisplayer
                fi
        done
}
 
#退出前的恢復
function MyExitNoSub()
{
        local y
 
        #恢復終端屬性
        stty $sTTY
        ((y = iTop + iTrayHeight + 4))
 
        #顯示光標
        echo -e "\033[?25h\033[${y};0H"
        exit
}
 
 
function MyExit()
{
        #通知顯示進程須要退出
        kill -$sigExit $pidDisplayer
 
        MyExitNoSub
}
 
 
#處理顯示和遊戲流程的主函數
function RunAsDisplayer()
{
        local sigThis
        InitDraw
 
        #掛載各類信號的處理函數
        trap "sig=$sigRotate;" $sigRotate
        trap "sig=$sigLeft;" $sigLeft
        trap "sig=$sigRight;" $sigRight
        trap "sig=$sigDown;" $sigDown
        trap "sig=$sigAllDown;" $sigAllDown
        trap "ShowExit;" $sigExit
 
        while :
        do
                #根據當前的速度級iLevel不一樣,設定相應的循環的次數
                for ((i = 0; i < 21 - iLevel; i++))
                do
                        sleep 0.02
                        sigThis=$sig
                        sig=0
 
                        #根據sig變量判斷是否接受到相應的信號
                        if ((sigThis == sigRotate)); then BoxRotate;        #旋轉
                        elif ((sigThis == sigLeft)); then BoxLeft;        #左移一列
                        elif ((sigThis == sigRight)); then BoxRight;        #右移一列
                        elif ((sigThis == sigDown)); then BoxDown;        #下落一行
                        elif ((sigThis == sigAllDown)); then BoxAllDown;        #下落到底
                        fi
                done
                #kill -$sigDown $$
                BoxDown        #下落一行
        done
}
 
 
#BoxMove(y, x), 測試是否能夠把移動中的方塊移到(x, y)的位置, 返回0則能夠, 1不能夠
function BoxMove()
{
        local j i x y xTest yTest
        yTest=$1
        xTest=$2
        for ((j = 0; j < 8; j += 2))
        do
                ((i = j + 1))
                ((y = ${boxCur[$j]} + yTest))
                ((x = ${boxCur[$i]} + xTest))
                if (( y < 0 || y >= iTrayHeight || x < 0 || x >= iTrayWidth))
                then
                        #撞到牆壁了
                        return 1
                fi
                if ((${iMap[y * iTrayWidth + x]} != -1 ))
                then
                        #撞到其餘已經存在的方塊了
                        return 1
                fi
        done
        return 0;
}
 
 
#將當前移動中的方塊放到背景方塊中去,
#並計算新的分數和速度級。(即一次方塊落到底部)
function Box2Map()
{
        local j i x y xp yp line
 
        #將當前移動中的方塊放到背景方塊中去
        for ((j = 0; j < 8; j += 2))
        do
                ((i = j + 1))
                ((y = ${boxCur[$j]} + boxCurY))
                ((x = ${boxCur[$i]} + boxCurX))
                ((i = y * iTrayWidth + x))
                iMap[$i]=$cBoxCur
        done
 
        #消去可被消去的行
        line=0
        for ((j = 0; j < iTrayWidth * iTrayHeight; j += iTrayWidth))
        do
                for ((i = j + iTrayWidth - 1; i >= j; i--))
                do
                        if ((${iMap[$i]} == -1)); then break; fi
                done
                if ((i >= j)); then continue; fi
 
                ((line++))
                for ((i = j - 1; i >= 0; i--))
                do
                        ((x = i + iTrayWidth))
                        iMap[$x]=${iMap[$i]}
                done
                for ((i = 0; i < iTrayWidth; i++))
                do
                        iMap[$i]=-1
                done
        done
 
        if ((line == 0)); then return; fi
 
        #根據消去的行數line計算分數和速度級
        ((x = iLeft + iTrayWidth * 2 + 7))
        ((y = iTop + 11))
        ((iScore += line * 2 - 1))
        #顯示新的分數
        echo -ne "\033[1m\033[3${cScoreValue}m\033[${y};${x}H${iScore}         "
        if ((iScore % iScoreEachLevel < line * 2 - 1))
        then
                if ((iLevel < 20))
                then
                        ((iLevel++))
                        ((y = iTop + 14))
                        #顯示新的速度級
                        echo -ne "\033[3${cScoreValue}m\033[${y};${x}H${iLevel}        "
                fi
        fi
        echo -ne "\033[0m"
 
 
        #從新顯示背景方塊
        for ((y = 0; y < iTrayHeight; y++))
        do
                ((yp = y + iTrayTop + 1))
                ((xp = iTrayLeft + 1))
                ((i = y * iTrayWidth))
                echo -ne "\033[${yp};${xp}H"
                for ((x = 0; x < iTrayWidth; x++))
                do
                        ((j = i + x))
                        if ((${iMap[$j]} == -1))
                        then
                                echo -ne "  "
                        else
                                echo -ne "\033[1m\033[7m\033[3${iMap[$j]}m\033[4${iMap[$j]}m[]\033[0m"
                        fi
                done
        done
}
 
 
#下落一行
function BoxDown()
{
        local y s
        ((y = boxCurY + 1))        #新的y座標
        if BoxMove $y $boxCurX        #測試是否能夠下落一行
        then
                s="`DrawCurBox 0`"        #將舊的方塊抹去
                ((boxCurY = y))
                s="$s`DrawCurBox 1`"        #顯示新的下落後方塊
                echo -ne $s
        else
                #走到這兒, 若是不能下落了
                Box2Map                #將當前移動中的方塊貼到背景方塊中
                RandomBox        #產生新的方塊
        fi
}
 
#左移一列
function BoxLeft()
{
        local x s
        ((x = boxCurX - 1))
        if BoxMove $boxCurY $x
        then
                s=`DrawCurBox 0`
                ((boxCurX = x))
                s=$s`DrawCurBox 1`
                echo -ne $s
        fi
}
 
#右移一列
function BoxRight()
{
        local x s
        ((x = boxCurX + 1))
        if BoxMove $boxCurY $x
        then
                s=`DrawCurBox 0`
                ((boxCurX = x))
                s=$s`DrawCurBox 1`
                echo -ne $s
        fi
}
 
 
#下落到底
function BoxAllDown()
{
        local k j i x y iDown s
        iDown=$iTrayHeight
 
        #計算一共須要下落多少行
        for ((j = 0; j < 8; j += 2))
        do
                ((i = j + 1))
                ((y = ${boxCur[$j]} + boxCurY))
                ((x = ${boxCur[$i]} + boxCurX))
                for ((k = y + 1; k < iTrayHeight; k++))
                do
                        ((i = k * iTrayWidth + x))
                        if (( ${iMap[$i]} != -1)); then break; fi
                done
                ((k -= y + 1))
                if (( $iDown > $k )); then iDown=$k; fi
        done
 
        s=`DrawCurBox 0`        #將舊的方塊抹去
        ((boxCurY += iDown))
        s=$s`DrawCurBox 1`        #顯示新的下落後的方塊
        echo -ne $s
        Box2Map                #將當前移動中的方塊貼到背景方塊中
        RandomBox        #產生新的方塊
}
 
 
#旋轉方塊
function BoxRotate()
{
        local iCount iTestRotate boxTest j i s
        iCount=${countBox[$iBoxCurType]}        #當前的方塊經旋轉能夠產生的樣式的數目
 
        #計算旋轉後的新的樣式
        ((iTestRotate = iBoxCurRotate + 1))
        if ((iTestRotate >= iCount))
        then
                ((iTestRotate = 0))
        fi
 
        #更新到新的樣式, 保存老的樣式(但不顯示)
        for ((j = 0, i = (${offsetBox[$iBoxCurType]} + $iTestRotate) * 8; j < 8; j++, i++))
        do
                boxTest[$j]=${boxCur[$j]}
                boxCur[$j]=${box[$i]}
        done
 
        if BoxMove $boxCurY $boxCurX        #測試旋轉後是否有空間放的下
        then
                #抹去舊的方塊
                for ((j = 0; j < 8; j++))
                do
                        boxCur[$j]=${boxTest[$j]}
                done
                s=`DrawCurBox 0`
 
                #畫上新的方塊
                for ((j = 0, i = (${offsetBox[$iBoxCurType]} + $iTestRotate) * 8; j < 8; j++, i++))
                do
                        boxCur[$j]=${box[$i]}
                done
                s=$s`DrawCurBox 1`
                echo -ne $s
                iBoxCurRotate=$iTestRotate
        else
                #不能旋轉,仍是繼續使用老的樣式
                for ((j = 0; j < 8; j++))
                do
                        boxCur[$j]=${boxTest[$j]}
                done
        fi
}
 
 
#DrawCurBox(bDraw), 繪製當前移動中的方塊, bDraw爲1, 畫上, bDraw爲0, 抹去方塊。
function DrawCurBox()
{
        local i j t bDraw sBox s
        bDraw=$1
 
        s=""
        if (( bDraw == 0 ))
        then
                sBox="\040\040"
        else
                sBox="[]"
                s=$s"\033[1m\033[7m\033[3${cBoxCur}m\033[4${cBoxCur}m"
        fi
 
        for ((j = 0; j < 8; j += 2))
        do
                ((i = iTrayTop + 1 + ${boxCur[$j]} + boxCurY))
                ((t = iTrayLeft + 1 + 2 * (boxCurX + ${boxCur[$j + 1]})))
                #\033[y;xH, 光標到(x, y)處
                s=$s"\033[${i};${t}H${sBox}"
        done
        s=$s"\033[0m"
        echo -n $s
}
 
 
#更新新的方塊
function RandomBox()
{
        local i j t
 
        #更新當前移動的方塊
        iBoxCurType=${iBoxNewType}
        iBoxCurRotate=${iBoxNewRotate}
        cBoxCur=${cBoxNew}
        for ((j = 0; j < ${#boxNew[@]}; j++))
        do
                boxCur[$j]=${boxNew[$j]}
        done
 
 
        #顯示當前移動的方塊
        if (( ${#boxCur[@]} == 8 ))
        then
                #計算當前方塊該從頂端哪一行""出來
                for ((j = 0, t = 4; j < 8; j += 2))
                do
                        if ((${boxCur[$j]} < t)); then t=${boxCur[$j]}; fi
                done
                ((boxCurY = -t))
                for ((j = 1, i = -4, t = 20; j < 8; j += 2))
                do
                        if ((${boxCur[$j]} > i)); then i=${boxCur[$j]}; fi
                        if ((${boxCur[$j]} < t)); then t=${boxCur[$j]}; fi
                done
                ((boxCurX = (iTrayWidth - 1 - i - t) / 2))
 
                #顯示當前移動的方塊
                echo -ne `DrawCurBox 1`
 
                #若是方塊一出來就沒處放,Game over!
                if ! BoxMove $boxCurY $boxCurX
                then
                        kill -$sigExit ${PPID}
                        ShowExit
                fi
        fi
 
 
 
        #清除右邊預顯示的方塊
        for ((j = 0; j < 4; j++))
        do
                ((i = iTop + 1 + j))
                ((t = iLeft + 2 * iTrayWidth + 7))
                echo -ne "\033[${i};${t}H        "
        done
 
        #隨機產生新的方塊
        ((iBoxNewType = RANDOM % ${#offsetBox[@]}))
        ((iBoxNewRotate = RANDOM % ${countBox[$iBoxNewType]}))
        for ((j = 0, i = (${offsetBox[$iBoxNewType]} + $iBoxNewRotate) * 8; j < 8; j++, i++))
        do
                boxNew[$j]=${box[$i]};
        done
 
        ((cBoxNew = ${colorTable[RANDOM % ${#colorTable[@]}]}))
 
        #顯示右邊預顯示的方塊
        echo -ne "\033[1m\033[7m\033[3${cBoxNew}m\033[4${cBoxNew}m"
        for ((j = 0; j < 8; j += 2))
        do
                ((i = iTop + 1 + ${boxNew[$j]}))
                ((t = iLeft + 2 * iTrayWidth + 7 + 2 * ${boxNew[$j + 1]}))
                echo -ne "\033[${i};${t}H[]"
        done
        echo -ne "\033[0m"
}
 
 
#初始繪製
function InitDraw()
{
        clear
        RandomBox        #隨機產生方塊,這時右邊預顯示窗口中有方快了
        RandomBox        #再隨機產生方塊,右邊預顯示窗口中的方塊被更新,原先的方塊將開始下落
        local i t1 t2 t3
 
        #顯示邊框
        echo -ne "\033[1m"
        echo -ne "\033[3${cBorder}m\033[4${cBorder}m"
 
        ((t2 = iLeft + 1))
        ((t3 = iLeft + iTrayWidth * 2 + 3))
        for ((i = 0; i < iTrayHeight; i++))
        do
                ((t1 = i + iTop + 2))
                echo -ne "\033[${t1};${t2}H||"
                echo -ne "\033[${t1};${t3}H||"
        done
 
        ((t2 = iTop + iTrayHeight + 2))
        for ((i = 0; i < iTrayWidth + 2; i++))
        do
                ((t1 = i * 2 + iLeft + 1))
                echo -ne "\033[${iTrayTop};${t1}H=="
                echo -ne "\033[${t2};${t1}H=="
        done
        echo -ne "\033[0m"
 
 
        #顯示"Score""Level"字樣
        echo -ne "\033[1m"
        ((t1 = iLeft + iTrayWidth * 2 + 7))
        ((t2 = iTop + 10))
        echo -ne "\033[3${cScore}m\033[${t2};${t1}HScore"
        ((t2 = iTop + 11))
        echo -ne "\033[3${cScoreValue}m\033[${t2};${t1}H${iScore}"
        ((t2 = iTop + 13))
        echo -ne "\033[3${cScore}m\033[${t2};${t1}HLevel"
        ((t2 = iTop + 14))
        echo -ne "\033[3${cScoreValue}m\033[${t2};${t1}H${iLevel}"
        echo -ne "\033[0m"
}
 
 
#退出時顯示GameOVer!
function ShowExit()
{
        local y
        ((y = iTrayHeight + iTrayTop + 3))
        echo -e "\033[${y};0HGameOver!\033[0m"
        exit
}
 
 
#顯示用法.
function Usage
{
        cat << EOF
Usage: $APP_NAME
Start tetris game.
 
  -h, --help              display this help and exit
      --version           output version information and exit
EOF
}
 
 
#遊戲主程序在這兒開始.
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
        Usage
elif [[ "$1" == "--version" ]]; then
        echo "$APP_NAME $APP_VERSION"
elif [[ "$1" == "--show" ]]; then
        #當發現具備參數--show時,運行顯示函數
        RunAsDisplayer
else
        bash $0 --show&        #以參數--show將本程序再運行一遍
        RunAsKeyReceiver $!        #以上一行產生的進程的進程號做爲參數
fi
"俄羅斯方塊"使用shell編寫的源碼(這是我從網絡上找到的代碼,不得不說寫着程序的哥們也太有才了~)

相關文章
相關標籤/搜索