bash腳本編程(一)

寫在前面:

    博客書寫牢記5W1H法則:What,Why,When,Where,Who,How。


本篇主要內容:

● bash腳本格式

● 知識回顧

    變量

    數據類型

    算數運算

    賦值

    隨機數

    變量類型定義

       數值

        只讀

        一維數組

● 條件測試

    [ ]與` `

     數值測試

    字符串測試

    文件測試

● 腳本執行結果返回值

● 位置參數變量

● 特殊變量

● 選擇執行

    if語句

● 用戶交互、錯誤排查

    read

    bash -n

    bash -x



bash腳本格式:

   (1)第一行,頂格,定義解釋器#!/bin/bash

   (2)以#開頭的行爲註釋信息,寫明腳本作用版本歷史版本等信息

   (3)代碼區塊前段應該以#註明代碼作用,方便後期閱讀修改

   (4)注意縮進,適度添加空白行

   (5)bash中儘量定義PATH和locale變量,以保證命令引用和輸出正確。即定義PATH= LANG=


知識回顧:

   變量:

      局部變量:定義在腳本方法中,也只在其中有效。(鳥哥的書中本地變量和局部變量是同一個含義,都是自定義的變量)

      本地變量:只在當前shell有效,不傳遞給子shell。直接定義的自定義變量。

      環境變量:在當前shell有效,並會傳遞給子shell。用export定義或修改爲環境變量。

      位置參數變量:在shell中可通過$#引用的參數字符

      特殊變量:shell預先定義的有一定含義的字符。如$?返回上一條命令的執行結果返回值;$#返回參數個數等。

   數據類型:

      字符型、數值型。shell是弱類型編程語言,默認會將所有變量都當做字符型。

   算數運算:

      +,-,*,/,%,**

      let VAR=EXPRESSION

      VAR=$[ EXPRESSION ]

      VAR=$(( EXPRESSION ))

      VAR=$( expr ARGU1 運算符 ARGU2 ... )

      注意:有些時候乘法符號需要轉義,如最後一種算數運算式。

   增強型賦值:

      變量進行算數運算後,又將結果回存至變量中:

         let I=$I+#與下面等同

         let I+=#

         自增:

            VAR=$[$VAR+1]

            let VAR+=1

            let VAR++

         自減:

            VAR=$[$VAR-1]

            let VAR-=1

            let VAR--

   隨機數:

      $RANDOM會返回0-32767之間的整數。

      利用$RANDOM取一定數值內的隨機數。

      如取0-99之間隨機數:$RANDOM*99/32767即可啦。

   變量類型定義:

      declare [-aixr] VAR

      數值:

         declare -i SUM=100+200;echo $SUM 定義SUM爲數值類型

         declare -x VAR 將VAR變爲環境變量,等同於export;恢復用 +x

      只讀:

         declare -r VAR 將VAR定義爲只讀,同readonly

      一維數組(array):

         數組定義:

           (1)declare -a ARR=(VALUE0 VALUE1 VALUE2 ...) :使用declare定義並賦值數組

           (2)declare -a ARR=($( COMMAND )) :使用命令引用爲數組賦值

           (3)declare -a ARR=([0]=VALUE0 [2]=VALUE2 ...) :單獨爲每個數組單元賦值

           (4)read -a ARR :使用read命令讀取用戶輸入的數組,空格間隔數組中單元

         數組引用:

            echo ${ARR[@]} :引用所有數組單元

            echo ${ARR[#]} :#爲數字,引用某一數組單元

            echo ${#ARR[@]} :#就是#,引用數組長度

            echo ${array[@]:n:m} :引用數組第n-m個單元

            echo ${!ARR[@]} :引用數組的所有下標

實例:

  #獲取/etc/passwd文件總第18行和第15行用戶的UID,並計算其UID的和。

[root@localhost shell]# cat userIDsum.sh 
#!/bin/bash
# read /etc/passwd file,then get 15th and 18th users UID,get SUM.
# by fred
# v 0.0.1
# 2016.3.14

# get UID for line 18 and line 15
UID_18=`cut -d: -f3 /etc/passwd | head -n 18 | tail -n1`
UID_15=`cut -d: -f3 /etc/passwd | head -n 15 | tail -n1`

# get SUM
SUM=$[ $UID_15 + $UID_18 ]

# print SUM
echo $SUM

  #計算/etc/rc.d/init.d/functions和/etc/inittab文件的空白行數之和

[root@localhost shell]# cat spaceline_SUM.sh 
#!/bin/bash
# get spacelines SUM of file /etc/rc.d/init.d/functions and file /etc/rc.d/init.d/network 
# by fred
# v 0.0.1
# 2016.3.14

# set filepath
FILE1='/etc/rc.d/init.d/functions'
FILE2='/etc/rc.d/init.d/network'

# get spacelines of files
FILE1_SPACELINE=`grep "^[[:space:]]*$" $FILE1 | wc -l`
FILE2_SPACELINE=`grep "^[[:space:]]*$" $FILE2 | wc -l`

# get sum
SUM=$[ $FILE1_SPACELINE + $FILE2_SPACELINE ]
echo "$SUM"


條件測試:

   判斷某需求是否滿足,並有選擇的執行其他代碼內容。

      (1)查看命令結果返回值:$?

         0:成功

         1-255:失敗

      (2)測試表達式

         test EXPRESSION 或 [ EXPRESSION ]

         ` EXPRESSION `

         注意:兩端空格不能省略

[ ]與` `

   參閱"help ["命令,可以得到以下內容:

   "["是內嵌命令test的同義詞,必須以字符"]"結尾,以匹配開始的"["。

   "[["會根據EXPRESSION的估值返回狀態0或1。EXPRESSION按照"test"的相同條件組成,也可以使用下列操作符連接:

      ( EXPRESSION ):返回EXPRESSION的值

      ! EXPRESSION:去反

      EXPR1 && EXPR2:與

      EXPR1 || EXPR2:或

   總結:主要有一下不同點:

類別[ ]` `
獲取幫助man testman bash 1700行左右
A=~B,A包含B不支持支持=~
&&、|| 表示與或不支持支持
-a -o 表示與或支持不支持
-N,文件在最後讀之後被修改modified不支持支持
其他,請自行 man


   數值測試:數值比較

      -eq:等於

      -ne:不等

      -gt:大於

      -lt:小於

      -ge:大於等於

      -le:小於等於

      補充:man 1 test可以看到相關內容


   字符串測試:

      -v VARNAME:變量已定義則爲真

      -z string:字符串長度爲0

      string或-n string:字符串長度非0

      string1 == string2或string1 = string2:=在test和[ ]語句中使用。

      string1 != string2

      string1 < string2:如果 string1 在當前語言環境的字典順序中排在 string2 之前則爲真。

      string1 =~ string2:string1中包含string2則爲真

      注意:字符串測試選擇使用` `或[],如“=~”的測試就只能在` `中正確執行;字符串最好在“”內,防止字符串爲空時導致的錯誤。字符串測試符號與字符串之間的空格必須保留。

   文件測試:

      存在與否

         -a:文件存在

         -e:文件存在

      類型:

         -b:塊設備

         -c:字符設備

         -d:目錄

         -f:普通文件

         -h或-L:軟鏈接

         -p:命名管道

         -S:套接字

      權限:

         -r:可讀

         -w:可寫

         -x:可執行

      特殊權限:

         -u:特殊權限SUID已設置

         -g:特殊權限SGID已設置

         -k:特殊權限sticky已設置

      文件內容:

         -s:文件大小非0

      從屬:

         -G:已設置可用屬組

         -O:已設置可用屬主

      時間戳:

         -N:最後讀取後有內容修改

      雙目測試:

         file1 -ef file2:兩文件指向同一文件系統的相同inode,即爲同一文件的硬鏈接

         file1 -nt file2:fiel1比file2新,特指mtime。或file1存在,file2不存在

         file1 -ot file2:file1比file2老,特指atime。或file1不存在,file2存在

      組合測試:

         邏輯運算:

            (1)&& || !

            (2)-a -o !

   實例:

  #但當前主機名爲空或爲localhost.localdomin時,將其設置爲www.fredme.com

[root@magedu ~]# cat hostname.sh 
#!/bin/bash
# if hostname is null or "localhost.localdomin",then set hostname "www.fredme.com"
# by fred
# 0.0.1
# 2016.3.15

# get hostname
hostName=`hostname`

# check if hostName is null or "localhost.localdomin" ,then set it
[ -z "$hostName" -o "$hostName" == "localhost.localdomin" ] && hostname "www.fredme.com" && echo "hostname is chaged to " && hostname || echo "hostname is not null or \"localhost.localdomin\""

[root@magedu ~]# hostname
localhost.localdomin
[root@magedu ~]# ./hostname.sh 
hostname is chaged to 
www.fredme.com
[root@magedu ~]# hostname
www.fredme.com
[root@magedu ~]# ./hostname.sh 
hostname is not null or "localhost.localdomin"


腳本的執行結果狀態返回值:

   如果未使用exit指定腳本的狀態返回值,那腳本的狀態返回值就是腳本中最後一條命令的執行結果返回值。

   如果使用exit [n]指定(n爲0-255之間),則腳本會立即終止退出,並將n作爲腳本執行結果返回值。

   注意:將exit放入()內,則只退出當前語句。

   如下:

[root@magedu shell]# cat exittest1.sh 
#!/bin/bash

[ -z $1 ] && echo "NULL" && exit 66
echo $?
echo "continue"
[root@magedu shell]# ./exittest1.sh ;echo $?
NULL
66

  # ↑↑ 正確執行,並返回狀態值

[root@magedu shell]# cat exittest2.sh 
#!/bin/bash

[ -z $1 ] && ( echo "NULL" && exit 66 ) && echo "line1 continue"
echo $?
echo "continue"
[root@magedu shell]# ./exittest2.sh 
NULL
66
continue

  # ↑↑ 邏輯判斷語句後面的語句不再執行,而下一行則繼續

[root@magedu shell]# cat e.sh 
#!/bin/bash

(
echo "nihao"
exit 0
echo "hello"
)
echo "ouhayi"
[root@magedu shell]# ./e.sh 
nihao
ouhayi

  # ↑↑ 這個例子同上,exit語句只是跳過了exit語句後()內的語句,後面的照常執行。


位置參數變量:

   在運行腳本時,在其後跟上參數,即可在腳本內以$1 ...來調用。

      SCRIPTS.sh ARG1 ARG2 ...

   輪轉:

      shift [n]:n爲數字,將第#個位置參數變成第#-n個,小於1的參數被丟棄。

特殊變量:

   $0:本腳本文件路徑,文件名

   $#:腳本參數個數

   $@:所有參數

   $*:所有參數

   實例:

  #用位置參數變量爲腳本傳遞2個文本文件,並計算空白行之和。

[root@magedu tmp]# cat spaceline_SUM.sh 
#!/bin/bash
# users give 2 files OPTION,then show the spaceline sum.
# exit 2: ARGS err
# exit 3: File not found or not readable

# check 2 ARGS is given
[ $# -gt 2 ] && echo "2 ARGS must be given.no MORE or less" && exit 2
[ $# -lt 2 ] && echo "2 ARGS must be given.no more or LESS" && exit 2

# get ARGS
FILE1=$1
FILE2=$2

# check files exsit and regular file ,then get spacelines number
[ -f $FILE1 -a -r $FILE1 ] && NUM1=`grep '^[[:space:]]*$' $FILE1 | wc -l` || echo "\"$FILE1\" not found or not readable"
[ -f $FILE2 -a -r $FILE2 ] && NUM2=`grep '^[[:space:]]*$' $FILE2 | wc -l` || echo "\"$FILE2\" not found or not readable"

# if NUM1 and NUM2 is set,show SUM,else exit
[ -n "$NUM1" -a -n "$NUM2" ] && SUM=$[ $NUM1 + $NUM2 ] && echo "There is $SUM spacelines in $FILE1 and $FILE2" || exit 3

[root@magedu tmp]# mknod device b 8 0
[root@magedu tmp]# cp /etc/fstab file1
[root@magedu tmp]# cp /etc/init.d/functions file2
[root@magedu tmp]# ls -l file1 file2 device 
brw-r--r--. 1 root root  8, 0 Mar 15 12:04 device
-rw-r--r--. 1 root root   595 Mar 15 12:05 file1
-rw-r--r--. 1 root root 13948 Mar 15 12:05 file2
[root@magedu tmp]# ./spaceline_SUM.sh file1000
2 ARGS must be given.no more or LESS
[root@magedu tmp]# ./spaceline_SUM.sh file1 device 
"device" not found or not readable
[root@magedu tmp]# ./spaceline_SUM.sh file1 file2
There is 71 spacelines in file1 and file2


選擇執行語句:

   順序執行:逐條執行

   選擇執行

      一個或多個分支,滿足條件則執行

      單分支if語句;

         if 測試條件;then

            代碼分支

         fi

      雙分支if語句;

         if 測試條件;then

            代碼分支

         else

            代碼分支

         fi

      多分支if語句:

         if 測試條件;then

            代碼分支

         elif 測試條件;then

            代碼分支

         ...

         else

            代碼分支

         fi

   循環執行:代碼片段(循環體)執行0次或多次

      後篇介紹

   實例:

  #參數傳遞用戶名,若用戶不存在,則添加。

[root@magedu tmp]# cat useradd.sh 
#!/bin/bash
# get USERNAME form ARGS, if USERNAME not exist ,add it
# exit 2: ARG err
#

# check ARG numbers
[ $# -gt 1 ] && echo "You can ONLY give 1 ARG" && exit 2
[ $# -lt 1 ] && echo "1 ARG NUST given" && exit 2

# get USERNAME
USERNAME=$1

# check if USERNAME not exist ,if not,add it.
if id $USERNAME &> /dev/null ;then
	echo "\"$USERNAME\" is already exist."
else
	useradd $USERNAME
	echo "$USERNAME added successful."
fi
[root@magedu tmp]# ./useradd.sh 
1 ARG NUST given
[root@magedu tmp]# ./useradd.sh fred
"fred" is already exist.
[root@magedu tmp]# ./useradd.sh fredme
fredme added successful.
[root@magedu tmp]# id fredme
uid=1006(fredme) gid=1006(fredme) groups=1006(fredme)


用戶交互,錯誤排查:

   read

      Read a line from the standard input and split it into fields.

      read [option]... [name ...]

      -p prompt:輸出提示信息

      -t timeout:設置超時時間

      -i text:用戶未輸入,將變量賦值爲text

   bash -n SCRIPTS:檢測腳本中的語法錯誤

   bash -x SCRIPTS:調試執行腳本

   實例:

  #編寫腳本實現以下功能:

   ./man.sh [page] COMMAND

   執行腳本,後跟一個參數,顯示此參數的man手冊

   後跟2個參數,直接顯示對應章節,章節無效提示用戶。

   如果有多個man手冊章節,則提示用戶輸入章節名,回車後顯示對應章節

   若只有一個man手冊章節,則直接顯示此章節

[root@www tmp]# cat man.sh 
#!/bin/bash
# 顯示man手冊,格式爲 man.sh [page] COMMAND,主要功能如下
# 若只給定COMMAND,查詢COMMAND的章節存在,則顯示章節並提示用戶輸入選擇,不存在則提示並退出;
# 若給定page和comman,判斷用戶給定的page頁是否存在,存在,則直接顯示,不存在提示退出。
# 若未給定任何參數,提示退出。
# by fred

PATH="/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
# 檢查參數個數,1個參數直接賦值給KEY,即當做命令關鍵字KEY;2個參數分別賦值給章節數PAGE,和命令關鍵字KEY,其他則直接提示退出腳本
if [ $# -eq 2 ];then
	KEY=$2
	PAGE=$1
elif [ $# -eq 1 ];then
	KEY=$1
else
	echo "Usage: $0 [PAGE] COMMAND"
	exit 1
fi

# 獲取命令man 章節號碼,並寫入到LIST屬組中。注意whatis的輸出中會存在非關鍵字的條目,相關條目也會輸出,如what is passwd會輸出sslpasswd的man章節,用grep過濾下!
declare -a LIST=(`whatis -w -r "^$KEY\>" 2> /dev/null | grep "^$KEY\>" | awk '{print $2}' | tr -d '()'`)
# 獲取man章節條數,並賦值給NUM變量
NUM=${#LIST[@]}
# 查找命令關鍵字章節條數,若爲0,則直接提示退出腳本
if [ $NUM -eq 0 ];then
	echo "There is no Manual Page about \"$KEY\"."
	exit 16
fi

# 運行到這,證明命令章節至少有1條。下面判斷用戶是否給出章節page,以及page是否有效
# 若用戶給定page,則判斷給定的page是否存在,使用YES變量標記。若存在,則繼續執行;不存在,則提示並退出腳本
YES=0
if [ $YES -eq 0 -a -n "$PAGE" ];then
	for P in ${LIST[@]};do
		[ "$P" == "$PAGE" ] && YES=1 && break
	done
	if [ $YES -ne 1 ];then
		echo "No manual entry for \"$KEY\" in section \"$PAGE\"."
		exit 2
	fi
fi
# 運行到此處有2種情況,1.用戶給定page,並且page章節存在;2.用戶未給定page。
# 1.用戶已給定page,並且給定的page章節存在
if [ $YES -eq 1 -a -n "$PAGE" ];then
	man $PAGE $KEY
# 針對用戶未給定page,輸出章節列表並提示用戶選擇
else
	echo "Manual pages found: "
	whatis -w $KEY | grep "^$KEY\>"
	read -i "${LIST[1]}" -t 5 -p "Please input page number to open: [Enter: default; n/N quit] " PAGE
	[ "$PAGE" == "N" -o "$PAGE" == "n" ] && exit 0
	echo "man $PAGE $KEY"
	man $PAGE $KEY
fi


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章