1. 程式人生 > >NO.3 Shell腳本

NO.3 Shell腳本

linux

編譯型語言:

程序在執行之前需要一個專門的編譯過程,把程序編譯成 為機器語言文件,運行時不需要重新翻譯,直接使用編譯的結果就行了。程序執行效率高,依賴編譯器,跨平臺性差些。如C、C++

解釋型語言:

程序不需要編譯,程序在運行時由解釋器翻譯成機器語言,每執 行一次都要翻譯一次。因此效率比較低。比如Python/JavaScript/ Perl /ruby/Shell等都是解釋型語言。

總結:

編譯型語言比解釋型語言速度較快,但是不如解釋性語言跨平臺性好。如果做底層開發或者大型應用程序或者操作系開發一般都用編譯型語言;如果是一些服務器腳本及一些輔助的接口,對速度要求不高、對各個平臺的兼容性有要求的話則一般都用解釋型語言。

回顧一下,Linux操作系統由什麽組成的?

內核、shell、應用程序、文件系統

shell:命令解釋器 人機交互的一個橋梁

終端——》命令

|

bash shell 解釋器(shell)

|

kernel

|

硬件

什麽是shell腳本?

簡單來說就是將需要執行的命令保存到文本中,按照順序執行它。它是解釋型的,意味著它不需要編譯。

若幹命令 + 腳本的基本格式 + 腳本特定語法 + 思想= shell腳本

什麽時候用到腳本?

重復化、復雜化的工作,通過把工作的命令寫成腳本,以後僅僅需要執行腳本就能完成這些工作。

如何學習腳本?

1、盡可能記憶更多的命令

2、掌握腳本的標準的格式(指定魔法字節、使用標準的執行方式運行腳本)

3、必須熟悉掌握腳本的基本語法(以下列表僅僅的基本要求,還有很多更深更難的語法需要自己擴充學習)

變量定義

條件判斷

分支語句

函數

數組

循環語句

正則表達式

sed,awk命令的使用

學習腳本的秘訣:

多看,多寫,多思考

看懂腳本——>模仿——>自己寫

腳本的編寫方法:

非標準:

source xxx.sh

. xxx.sh

bash xxx.sh

sh xx.sh

標準:

格式相對完整的腳本,建議的格式。

#!/bin/bash 腳本第一行 , #!魔法字符,指定腳本代碼執行的程序。即它告訴系統這個腳本需要什麽解釋器來執行,也就是使用哪一種Shell

Name: 名字

Desc:描述describe

Path:存放路徑

Usage:用法

Update:更新時間

。。。。

腳本執行方法:

標準腳本執行方法(建議):(魔法字節指定的程序會生效)

非標準的執行方法(不建議):(魔法字節指定的程序不會運作)

總結:

./xxx.sh --要求有執行權限,並且一定要聲明shell類型(#!/bin/bash)

. xxx.sh或者source xxx.sh或者bash xxx.sh或者sh xxx.sh --不需要有執行權限,也可以不聲明shell類型

說明: bash -x xxx.sh 或者sh -x xxx.sh --可以顯示執行過程,幫助排錯

補充:

bash中的引號:

雙引號 "" 會把引號的內容當成整體來看待,允許通過$符號引用其他變量值

單引號 ‘‘ 會把引號的內容當成整體來看待,禁止引用其他變量值,shell中特殊符號都被視為普通字符

反撇號 `` 和$() 反撇號和括號裏的命令會優先執行,如果存在嵌套,反撇號不能用。

; 可對一行命令進行分割,在執行過程中不考慮上一個命令執行是否是正確的

&& 邏輯與。可對一行命令進行分割,在執行過程中考慮上一個命令執行是否是正確的

|| 邏輯或

變量的分類:

本地變量:當前用戶自定義的變量。當前進程中有效,其他進程及當前進程的子進程無效。

unset a 取消變量

環境變量:當前進程有效,並且能夠被子進程調用。

HI=hello 設置一個本地變量

查看當前用戶的環境變量 env

env |grep HI

查詢當前用戶的所有變量<臨時變量與環境變量>

set |grep HI

HI=hello

export HI 將當前變量變成環境變量

env |grep -i HI

HISTSIZE=1000

HI=hello

全局變量:全局所有的用戶和程序都能調用,且繼承,新建的用戶也默認能調用。

/etc/profile

HI=Hello

export HI

/etc/profile

/etc/bashrc

~/.bash_profile

~/.bash_bashrc

user——>login——>/etc/profile——>~/.bash_profile——>/etc/bashrc——>~/.bashrc——>~/.bash_logout

局部變量:

~/.bash_profile

...

註意:需要重新登錄才生效

$HOME/.bashrc 當前用戶固定變量 eg:別名

$HOME/.bash_profile 當前用戶的環境變量

/etc/bashrc 使用bash shell用戶全局變量

/etc/profile 使用所有shell的全局變量

系統變量(內置bash中變量) : shell本身已經固定好了它的名字和作用。$@,$*,$# ,$$ ,$? ,$0

$#:腳本後面接的參數的個數

$*:腳本後面所有參數

$@: 腳本後面所有參數

$?:上一條命令執行後返回的狀態,當返回狀態值為0時表示執行正常,非0值表示執行異常或出錯

若退出狀態值為0 表示命令運行成功

若退出狀態值為127 command not found

若退出狀態值為126 找到了該命令但無法執行 ---權限不夠

若退出狀態值為1&2 沒有那個文件或目錄

$$ 當前所在進程的進程號

$! 後臺運行的最後一個進程號 (當前終端)

!$ 調用最後一條命令歷史中的參數

!! 調用最後一條命令歷史

$0 當前執行的進程/程序名

$1~$9 位置參數變量

${10}~${n} 擴展位置參數變量 第10個位置變量必須用{}大括號括起來

vim 3.sh

#!/bin/bash

#xxxxx

echo "\$0 = $0"

echo "\$# = $#"

echo "\$ = $"

echo "\$@ = $@"

echo "\$1 = $1"

echo "\$2 = $2"

echo "\$3 = $3"

echo "\$10 = ${10}"

什麽時候用到變量?

如果某個內容需要多次使用,並且在代碼中重復出現,那麽可以用變量代表該內容。這樣在修改內容的時候,僅僅需要修改變量的值

在代碼運作的過程中,可能會把某些命令的執行結果保存起來,後續代碼需要使用這些結果,就可以直接使用這個變量

變量定義的規則:

1、默認情況下,shell裏定義的變量是不分類型的,可以給變量賦與任何類型的值;等號兩邊不能有空格,對於有空格的字符串做為賦值時,要用引號引起來

955 A=hello

956 echo $A

957 A= hello

958 A =hello

959 A = hello

960 A=hello world

961 A=‘hello world‘

962 echo $A

963 A="hello world"

964 echo $A

2、變量的獲取方式: $變量名 ${變量名}

966 echo $A

967 echo ${A}

968 a=123456

969 echo $a

970 echo ${a}

971 echo ${a:2:3}

972 a=123456789

973 echo ${a:3:5} 3代表從第3位開始截取;5代表截取5個數

975 echo ${a:3} 代表截取從第3位開始以後所有的

3、取消變量的命令 unset 變量名

4、區分大小寫,同名稱但大小寫不同的變量名是不同的變量

5、變量名可以是字母或數字或下劃線,但是不能以數字開頭或者特殊字符

[root@node1 shell01]# a=1abc

[root@node1 shell01]# 1a=hello

bash: 1a=hello: command not found

[root@node1 shell01]# ?a=hello

bash: ?a=hello: command not found

[root@node1 shell01]# /a=hello

bash: /a=hello: No such file or directory

[root@node1 shell01]# _a=777

[root@node1 shell01]# echo $_a

777

6、命令的執行結果可以保存到變量

註意:

$( ) 等同於 執行符號 `,但是如果要嵌套使用,使用 符號就不行,要用$();但如果不是嵌套的使用 是可以的,如a="which mount`which yum"

7、數組

數組定義:用括號來表示數組,數組元素用“空格”符號分割開。定義數組的一般形式為:

array=(var1 var2 var3 var4)

或者

array[0]=v1

array[1]=v2

array[3]=v3

讀取數組:

${array [i]} i表示元素

使用@ 或 * 可以獲取數組中的所有元素:

hello,stu1

hello,stu2

hello,stu3

#!/bin/bash

array=(stu1 stu2 stu3)

for var in ${array[*]}

do

echo hello,$var

done

[root@node1 shell01]# var[0]=user1

[root@node1 shell01]# var[1]=user2

[root@node1 shell01]# var[2]=user3

[root@node1 shell01]# for i in ${var[@]};do echo hello,$i;done

hello,user1

hello,user2

hello,user3

獲取第n個元素

echo "${user[N-1]}"

獲取數組指定元素

echo ${user[@]:1:3} 從數組下標為1開始,讀取3個元素

示例:

定義一組用戶u01~u05,分別在屏幕上顯示hello,username

8、有類型變量

declare

-i 將變量看成整數

-r 使變量只讀 readonly

-x 標記變量通過環境導出 export

-a 將變量看成數組

[root@node1 shell01]# a=10

[root@node1 shell01]# b=2

[root@node1 shell01]# c=$a+$b

[root@node1 shell01]# echo $c

10+2

[root@node1 shell01]# declare -i a=5

[root@node1 shell01]# declare -i b=2

[root@node1 shell01]# declare -i c=$a+$b

[root@node1 shell01]# echo $c

7

[root@node1 shell01]#

[root@node1 shell01]# declare -i a=10 b=2

[root@node1 shell01]# declare -i c=$a*$b

[root@node1 shell01]# echo $c

20

1040 echo $a

1041 declare -r a=hello

1042 echo $a

1043 declare -r A=hello

1044 A=888

1045 echo $A

1046 declare -i A=123

1047 AB=hello

1048 export AB

1049 env|grep AB

1050 declare -x ABC=hahaha

1051 env|grep ABC

9、交互式定義變量的值 read 主要用於讓用戶去定義變量值

-p 提示信息

-n 字符數

-s 不顯示

-t 超時(默認單位秒) read -t 5 a

1054 read -p "Input your name:" name

1055 echo $name

1056 read -s -p "Input your password:" pass

1057 echo $pass

1058 read -n 5 -p "Input your name:" name

1059 echo $name

1060 read -t 3 -p "Input your name:" name

1061 echo $name

1062 read -t 3 -p "Input your name:" name

1063 echo $name

10、其他變量:

一個“#”代表從左往右去掉一個指定字符

兩個“#”代表從左往右最大去掉指定字符

一個“%”代表從右往左去掉一個指定字符

兩個“%”代表從右往左最大去掉指定字符

取出一個目錄下的目錄和文件

A=/root/Desktop/shell/mem.txt

echo $A

/root/Desktop/shell/mem.txt

dirname $A 取出目錄

/root/Desktop/shell

basename $A 取出文件

mem.txt

echo ${A%/} 從右往左去掉“/”內容

/root/Desktop/shell

echo ${A%%.} 從右往左最大長度去掉.後的內容

/root/Desktop/shell/mem

echo ${A%%.txt} 從右往左最大長度去掉.txt內容

/root/Desktop/shell/mem

echo ${A##//} 從左往右最大去掉所有"//"

mem.txt

echo ${A#/*/}

Desktop/shell/mem.txt

1071 aaa=/shell/shell01/dir1/file.txt

1072 echo $aaa

1073 dirname $aaa

1074 basename $aaa

1075 echo ${aaa#/*/}

1076 echo ${aaa##/*/}

1077 echo ${aaa%.*}

1078 echo ${aaa%%.*}

1079 echo ${aaa%/*/}

1080 echo ${aaa%/*}

1081 echo ${aaa%%/*}

===變量內容的替換===

[root@vm1 Desktop]# a=www.taobao.com

[root@vm1 Desktop]# echo ${a/taobao/baidu}

www.baidu.com

[root@vm1 Desktop]# a=www.taobao.com

[root@vm1 Desktop]# echo ${a/a/A}

www.tAobao.com

[root@vm1 Desktop]# echo ${a//a/A} 貪婪替換

www.tAobAo.com

===變量的替代===

echo ${var1-aaaaa}

aaaaa

var2=111

echo ${var2-bbbbb}

111

var3=

echo ${var3-ccccc}

${變量名:-新的變量值}

變量沒有被賦值(包括空值):都會使用“新的變量值“ 替代

變量有被賦值: 不會被替代

簡單的四則運算

算術運算:

默認情況下,shell就只能支持簡單的整數運算

      • / %(取模,求余數)

$(()) | $[] | expr | let

Bash shell 的算術運算有四種方式:

1、使用 $(( ))

2、使用$[ ]

3、使用 expr 外部程式

4、使用let 命令

加法:

n=10

let n=n+1

或者let n+=1

echo $n

乘法:

let m=n*10

echo $m

除法:

let r=m/10

echo $r

求余數:

let r=m%7

echo $r

乘冪:

let r=m**2

echo $r

註意:

n=1

let n+=1 等價於let n=n+1

思考:能不能用shell做小數運算?

echo 1+1.5|bc

2.5

i++ 和 ++i (了解)

對變量的值的影響:

[root@vm1 Desktop]# i=1

[root@vm1 Desktop]# let i++

[root@vm1 Desktop]# echo $i

2

[root@vm1 Desktop]# j=1

[root@vm1 Desktop]# let ++j

[root@vm1 Desktop]# echo $j

2

對表達式的值的影響:

[root@vm1 Desktop]# unset i j

[root@vm1 Desktop]# i=1

[root@vm1 Desktop]# j=1

[root@vm1 Desktop]# let x=i++ 先賦值,再運算

[root@vm1 Desktop]# echo $i

2

[root@vm1 Desktop]# echo $x

1

[root@vm1 Desktop]# let y=++j 先運算,再賦值

[root@vm1 Desktop]# echo $j

2

[root@vm1 Desktop]# echo $y

2


條件判斷

語法結構:

if [ condition ];then

command

command

fi


if [ condition ];then

command1

else

command2

fi


if [ condition1 ];then

command1 結束

elif [ condition2 ];then

command2 結束

else

command3

fi

如果條件1滿足,執行命令1後結束;如果條件1不滿足,再看條件2,如果條件2滿足執行命令2;如果條件1和條件2都不滿足執行命令3.

if [ condition1 ];then

command1

if [ condition2 ];then

command2

fi

else

if [ condition3 ];then

command3

elif [ condition4 ];then

command4

else

command5

fi

fi

如果條件1滿足,執行命令1;如果條件2也滿足執行命令2,如果不滿足就只執行命令1結束;

如果條件1不滿足,不看條件2;直接看條件3,如果條件3滿足執行命令3;如果不滿足則看條件4,如果條件4滿足執行命令4;否則執行命令5

判斷語法:

1、test 條件表達式

2、[ 條件表達式 ]

3、[[ 條件表達式 ]] 匹配正則 =~

cat if3.sh

#!/bin/bash

aaa=$1

[[ $aaa = ‘hello‘ ]] && echo world

[root@node1 shell01]# bash -x if3.sh

  • aaa=

  • ‘[‘ = hello ‘]‘

if3.sh: line 3: [: =: unary operator expected

[root@node1 shell01]# bash -x if3.sh hello

  • aaa=hello

  • ‘[‘ hello = hello ‘]‘

  • echo world

world

[root@node1 shell01]# vim if3.sh

[root@node1 shell01]# bash -x if3.sh

  • aaa=

  • [[ ‘‘ = \h\e\l\l\o ]]

man test去查看,很多的參數都用來進行條件判斷

與文件存在與否的判斷

-e 是否存在 不管是文件還是目錄,只要存在,條件就成立

-f 是否為普通文件

-d 是否為目錄

-S socket

-p pipe

-c character

-b block

-L 軟link

文件權限相關的判斷

-r 當前用戶對其是否可讀

-w 當前用戶對其是否可寫

-x 當前用戶對其是否可執行

-u 是否有suid

-g 是否sgid

-k 是否有t位

-s 是否為空白文件 說明:-s表示非空,! -s 表示空文件

[ -s file1 ] file1文件內容不為空,條件成立

[ ! -s file1 ] file1文件內容為空,條件成立

兩個文件的比較判斷

file1 -nt file2 比較file1是否比file2新

file1 -ot file2 比較file1是否比file2舊

file1 -ef file2 比較是否為同一個文件,或者用於判斷硬連接,是否指向同一個inode

整數之間的判斷

-eq 相等

-ne 不等

-gt 大於

-lt 小於

-ge 大於等於

-le 小於等於

字符串之間的判斷

-z 是否為空字符串 字符串長度為0,就成立

-n 是否為非空字符串 只要字符串非空,就是成立

string1 = string2 是否相等

string1 != string2 不等

! 結果取反

多重條件判斷

邏輯判斷符號:

-a 和 && 邏輯與 [ 條件1 -a 條件2 ] 只有兩個條件都成立,整個大條件才成立

[ 1 -eq 1 ] && [ 2 -ne 3 ]

[ 1 -eq 1 -a 2 -ne 3 ]

-o 和 || 邏輯或 [ 條件1 -o 條件2 ] 只要其中一個條件成立,整個大條件就成立

[ 1 -eq 1 -o 2 -ne 2 ]

[ 1 -eq 1 ] || [ 2 -ne 2 ]

! 邏輯非 優先級最低

-a 優先級 比 -o 優先級要高

cat if4.sh

#!/bin/bash

aaa=id -u

[ $aaa -eq 0 ] && echo "當前是超級用戶" || echo "you不是超級用戶"

[ $(id -u) -eq 0 ] && echo "當前是超級用戶"

$ [ $UID -eq 0 ] && echo "當前是超級用戶" || echo "you不是超級用戶"

((1==2));echo $? C語言風格的數值比較

((1>=2));echo $?

demo1:

判斷一個IP是否通ping通

方法1:

#!/bin/bash

read -p "請輸入你要ping額IP地址:" ip

ping -c1 $ip &>/dev/null 或者 >/dev/null 2>&1

if [ $? -eq 0 ];then

echo "當前主機與所輸入的IP網絡ok"

else

echo "當前主機與所輸入的IP網絡不ok"

fi

方法2:

ping -c1 $1 &>/dev/null

test $? -ne 0 && echo "當前主機與所輸入的IP網絡不ok" || echo "當前主機與所輸入的IP網絡ok"

demo2:

判斷一個進程是否存在

1、ps -ef|grep vsftpd |grep -v grep

2、pidof 程序名稱

3、pgrep -l 程序名稱

#!/bin/bash

ps -ef|grep vsftpd|grep -v grep &>/dev/null

if [ $? -eq 0 ];then

echo "該進程存在"

else

echo "該進程不存在"

fi

read -p "請輸入你需要判斷的進程名字:" name

pidof $name &>/dev/null

[ $? -eq 0 ] && echo "該進程存在" || echo "該進程不存在"

pidof $1 &>/dev/null

test $? -ne 0 && echo "該進程不存在" || echo "該進程存在"

需求:使用該腳本判斷所輸入的進程是否存在(多個進程名,至少2個)

#!/bin/bash

[ $# -eq 0 ] && echo "該腳本$0的用法是:$0 pidname" || pidname=($*)

for i in ${pidname[@]}

do

pidof $i &>/dev/null

test $? -ne 0 && echo "該進程不存在" || echo "該進程存在"

done

pgrep命令:以名稱為依據從運行進程隊列中查找進程,並顯示查找到的進程id

選項

-o:僅顯示找到的最小(起始)進程號;

-n:僅顯示找到的最大(結束)進程號;

-l:顯示進程名稱;

-P:指定父進程號;pgrep -p 4764 查看父進程下的子進程id

-g:指定進程組;

-t:指定開啟進程的終端;

-u:指定進程的有效用戶ID。

demo3:

判斷一個服務是否正常(以httpd為例):

1、可以判斷進程是否存在,用/etc/init.d/httpd status判斷狀態等方法

2、最好的方法是直接去訪問一下,通過訪問成功和失敗的返回值來判斷

#!/bin/bash

wget -P /tmp http://localhost &>/dev/null

[ $? -eq 0 ] && echo "該服務正常" || echo "該服務不正常"

課堂練習:

1、寫一個腳本判斷一個用戶是否存在

2、完善上一個腳本的bug,要求當沒有給腳本傳參數或者參數個數不等於1個時,提示腳本的用法:usage:xxx.sh ip

#!/bin/bash

[ $# -ne 1 ] && echo "usage:basename $0 username" && exit

id $1 &>/dev/null

test $? -eq 0 && echo "該用戶$1存在" || echo "該用戶$1不存在"

#!/bin/bash

[ $# -ne 1 ] && echo "usage:basename $0 ipaddr" && exit

wget http://$1 &>/dev/null

[ $? -eq 0 ] && echo ‘httpd服務正常‘ || echo "httpd服務不正常"

3、判斷vsftpd軟件包是否安裝,如果沒有則自動安裝

4、判斷vsftpd服務是否啟動,如果啟動,輸出以下信息:

vsftpd服務器已啟動...

vsftpd監聽的地址是:

vsftpd監聽的端口是:

#!/bin/bash

ftp=vsftpd

rpm -q $ftp &>/dev/null

[ $? -ne 0 ] && yum -y install $ftp &>/dev/null

pgrep -l $ftp &>/dev/null

[ $? -ne 0 ] && service $ftp start &>/dev/null && echo "服務已經啟動..."

ip=netstat -nltp|grep $ftp|cut -d: -f1|cut -c21-

port=netstat -nltp|grep $ftp|cut -d: -f2|cut -d‘ ‘ -f1

echo "vsftpd監聽的地址是:$ip"

echo "vsftpd監聽的端口是:$port"

作業:

1、 判斷/tmp/run文件是否存在,如果不存在就建立,如果存在就刪除目錄裏所有文件

#!/bin/bash

dir=/tmp/run

[ -d $dir ] && rm -rf $dir/* || mkdir $dir

dir=/tmp/run

[ -f $dir ] && mv $dir $dir.bak

[ -d $dir ] && rm -rf $dir/* || mkdir $dir

2、 輸入一個路徑,判斷路徑是否存在,而且輸出是文件還是目錄,如果是字符連接,還得輸出是有效的連接還是無效的連接

#!/bin/bash

read -p "請輸入絕對路徑:" path

if [ -e "$path" -a -L "$path" ];then

echo "該文件是一個有效連接文件"

elif [ ! -e "$path" -a -L "$path" ];then

echo "該文件是一個無效連接文件"

elif [ -d $path ];then

echo "該文件是一個目錄"

elif [ -f $path ];then

echo "該文件是一個普通文件"

elif [ -e $path ];then

echo "其他文件"

else

echo "路徑不存在"

fi

3、交互模式要求輸入一個ip,然後腳本判斷這個IP 對應的主機是否 能ping 通,輸出結果類似於:

Server 10.1.1.20 is Down! 最後要求把結果郵件到本地管理員root@localhost和mail01@localhost

4、寫一個腳本/home/program,要求當給腳本輸入參數hello時,腳本返回world,給腳本輸入參數world時,腳本返回hello。而腳本沒有參數或者參數錯誤時,屏幕上輸出“usage:/home/program hello or world”

#!/bin/bash

if [ "$1" = "hello" ];then

echo world

elif [ "$1" = "world" ];then

echo hello

elif [ $# -ne 1 ];then

echo "usage:$0 hello or world"

else

echo "usage:$0 hello or world"

fi

5、寫一個腳本自動搭建nfs服務

#!/bin/bash

#1.安裝軟件

#2.確認軟件是否安裝

#3.配置

#(1).新建共享目錄,授本地權限

#(2).發布共享目錄/etc/exports

#4.啟動服務

#5.設置下次開機自動啟動

#配置網絡,測試網絡

ping -c 1 192.168.0.254 &> /dev/null && echo "##############網絡OK###############"

#配置network yum

rm -fr /etc/yum.repos.d/*

cat > /etc/yum.repos.d/dvd.repo << EOT

[base]

baseurl=ftp://192.168.0.254/rhel6_dvd

gpgcheck=0

EOT

#1.安裝軟件

yum -y install nfs* rpcbind &> /dev/null && echo "##############軟件安裝OK###############"

#2.xx

#3.配置

#(1).新建共享目錄,授本地權限

#read -p "請輸入你的共享目錄" dir

mkdir -p $1

chmod 777 $1 && echo "##############本地授權OK###############"

NO.3 Shell腳本