您好,登錄后才能下訂單哦!
bash及shell腳本編程基礎
bash特性之多命令執行:使用分號分隔,命令之間無關系;
]# cmd
方式一:]# cmd1 `cmd2`:命令引用實現多命令;
方式二:]# cmd1|cmd2|cmd3|...:管道實現多命令;
方式三:]# cmd1;cmd2;cmd3;...:分號實現多命令;
邏輯組合:操作的是命令的運行狀態結果即退出碼;
]# cmd1 && cmd2 && ...
]# cmd1 || cmd2 ||...
]# !cmd1
退出碼:
0:表示為true,真,success,成功;
1-255:表示為failure,假,錯誤;
邏輯運算:主要操作的是命令的運行狀態結果即退出碼;
可認為有一種判斷的機制在里面;判斷取決于是與運算還是或運算還取決于第一個操作的結果;
運算數:true(1),false(0)
COMMAND運行狀態結果:
0:TRUE,成功;
1-255:FALSE,錯誤;
與:見false(0)為false(0);相當于乘法;
true && true = true
true && false = false
第一個操作數為true,其結果取決于第二個操作數;
false && true = false
false && false = false
第一個操作數為false,其結果至此可判定為false;
例如:
]# ls /var && cat /etc/fstab
]# lls /var && cat /etc/fstab
或:見true(1)為true(1);相當于加法;
true || true = true
true || false = true
第一個操作數為true,其結果至此可判定為ture;
false || true = true
false || false = false
第一個操作數為false,其結果取決于第二個操作數;
例如:
]# id hive || useradd hive:如果用戶不存在,則添加用戶;
]# id hive
非:取反
! true = false
! fase = true
例如:
]# ! id hive && useradd hive:如果用戶不存在,則添加用戶;
優先級:非 (高)<--與 <--或(低)
運行腳本:
(1)賦予執行權限,并直接運行此程序文件;
chmod +x /PATH/TO/SCRIPT_FILE
/PATH/TO/SCRIPT_FILE
也可直接把腳本文件放在PATH環境變量中;例如把腳本文件放在/bin目錄下;
(2)直接運行解釋器,以腳本文件為參數;
bash /PATH/TO/SCRIPT_FILE
-x:調試執行;
-n:判斷語法錯誤;
bash特性之變量:
引號有三種類型:'',"",``
引號:字符串引用符號;
''單引號:強引用;其內部的變量不會替換;
""雙引號:弱引用;其內部的變量會被替換;
``反引號:命令引用符號;
例如:
]# echo '$PATH'
顯示結果:$PATH
]# echo "$PATH"
顯示結果:/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
]# echo `ls /root`
顯示結果:anaconda-ks.cfg install.log install.log.syslog
變量引用:${NAME},要使用花括號
變量:是內存空間,有名稱,名稱即為變量名,對應的內存空間中的數據即為變量的值;
變量賦值:NAME=VALUE
=:賦值符號;
把VALUE存儲到NAME指向的內存空間中;
編程語言:
強類型:嚴格區分變量(內存空間)中的數據類型;
弱類型:不區分變量中存儲的數據類型,統一為字符型;
bash:統統默認為字符型數據,變量無需事先聲明;
變量為什么有類型?
存儲空間、存儲格式、參與的運算、...
變量命名:只能使用字母、數字和下劃線;且不能以數字開頭;
變量名:見明知義,不能使用程序保留字(如if,then,case,fi,esac,for,while,until,break,continue等等);
變量引用:${NAME},$NAME
變量替換:把變量引用符號出現的位置替換為其指向的內存空間中的數據;
bash的變量種類:
根據作用域劃分:作用域:生效范圍,可引用到的范圍;
變量目的:
(1)變量用于存數據,可重復多次使用這個數據;
(2)可多次修改變量里面的數據,以達到演進、迭代目的;
本地變量
環境變量
局部變量(函數中)
位置參數變量:
特殊變量:$?,, $@, $#
retval=$?
保存的是命令執行的狀態結果;
exit結束shell進程;
pstree命令:顯示進程樹;
tree命令:顯示目錄樹;
本地變量:作用域為當前shell進程;不包括其子進程;
定義本地變量:
本地變量賦值:NAME=VALUE
本地變量引用方式:$NAME,${NAME}
""
查看變量:set
撤銷變量:unset NAME
注意:此處非為變量引用,因此不能使用$;
所有的本地變量在shell進程終止時,會被自動撤銷;
例如:
]# pstree:查看shell進程樹;
]# name="obama":定義本地變量;僅當前shell進程有效;
]# echo $name
切換到另外的進程查看name變量:
]# echo $name:內容為空;本地變量在同級進程或子進程都無效;
]# set:查看本地變量;
]# unset name:撤銷本地name變量;此處非為變量引用,因此不能使用$;
]# animal="goat"
]# echo "There are some $(animal)s."
環境變量:作用域為當前shell進程及其子進程;但不包括同級進程;
環境變量聲明和賦值:
declare -x NAME[=VALUE]
-r:定義為只讀變量;
export NAME[=VALUE]
可以把本地變量聲明環境變量;
環境變量引用方式:
${NAME},$NAME
注意:bash內嵌了很多環境變量,名稱為全大寫字母;例如:HOME,UID,PWD,SHELL,PATH,HISTSIZE等等;
主要用于工作的特殊環境;
查看環境變量命令:
export,declare -x
env,printenv
撤銷環境變量:
unset NAME
例如:
]# declare -x animal:聲明環境變量;
]# /bin/bash:環境變量在當前shell進程及其子進程有效;
]# echo ${animal}:花括號可省略;
]# export:查看環境變量;
]# declare -x:查看環境變量;
]# env:查看環境變量;顯示格式不同;
]# printenv:查看環境變量;顯示格式不同;
]# unset namimal:撤銷環境變量;
例如:
]# ls /etc
]# retval=$?:把環境變量的值存儲在變量中,方便使用;
]# lll /etc
]# echo $?:查看環境變量狀態返回值;
]# echo $retval:查看之前的環境變量狀態返回值,這就是變量的意義;
只讀變量:就是常量,無法修改,無法撤銷;生命周期同當前shell;
定義只讀變量:不支持重新賦值,也不支持撤銷;
(1)declare -r NAME
(2)readonly NAME
例如:
]# username="Lu Youjiao"
]# declare -r username:定義只讀變量;
]# username="Ou Yangfeng":顯示不能更改只讀變量;
]# unset username:無法撤銷只讀變量;
bash腳本編程之算術運算
+,-,*,/,%(莫,判斷單雙數),**
shell變量是一種很弱的變量,默認情況下,一個變量保存一個串,shell不關心這個串是什么含義,所以弱要進行數學運算,必須使用一些命令例如let,declare,expr,雙括號等;
算術運算
+,-,*,/,%(取模,判斷單雙數),**
shell變量是一種很弱的變量,默認情況下,一個變量保存一個串,shell不關心這個串是什么含義,所以弱要進行數學運算,必須使用一些命令例如let,declare,expr,雙括號等;
算術運算格式語法:
(1)let VAR=$num1 op $num2(算術運算表達式)
(2)VAR=$[算術運算表達式]
(3)VAR=$((算術運算表達式))
(4)VAR=$(expr argu1 argu2 argu3)
注意:有些時候乘法符號需要轉義;
練習:腳本完成,添加3個用戶,求三用戶的ID之和;
id user1 &> /dev/null || useradd user1
id user2 &> /dev/null || useradd user2
user1_id=$(id -u user1)
user2_id=$(id -u user2)
id_sum=$[$user1_id+$user2_id]
echo "the id sum: $id_sum"
增強型變量賦值:
+=,-=,*=,/=,%=
自身等于自身+數值,使用let命令;
例如:
]# declare -i i=1
]# i=$[$i+1]
]# echo $i
此時就可用增強型賦值:
]# let i+=1
變量做某種算術運算后回存至此變量中;
let i=$i+#
let i+=#
#:代表數字
自增:
VAR=$[$VAR+1]
let VAR+=1
let VAR++
自減:
VAR=$[$VAR-1]
let VAR-=1
let VAR--
練習:
1、腳本實現,計算/etc/passwd文件中第15個用戶和第18個用戶的ID號之和;
分解:做a,b2個id號的運算,id_sum=$[$id1+$id2]
a=$(head -15 /etc/passwd | tail -1 | cut -d: -f3)
b=$(head -18 /etc/passwd | tail -1 | cut -d: -f3)
sum=$[$a+$b]
echo "the a is:$a"
echo "the b is :$b"
echo "teh sum is :$sum"
2、計算/etc/rc.d/init.d/functions和/ect/inittab文件中的空白行數之和;
a=$(egrep "^[[:space:]]*$" /etc/rc.d/init.d/functions|wc -l)
b=$(egrep "^[[:space:]]*$" /etc/rc.d/init.d/network|wc -l)
sum=$[$a+$b]
echo "a is :$a"
echo "b is $b"
echo "sum is :$sum"
bash腳本編程之條件測試:
判斷某需求是否滿足,需要由測試機制來實現;
如何編寫測試表達式以實現所需的測試:
例如:判斷某文件是否有空白行、判斷某用戶是否存在
(1)執行命令,并利用命令狀態返回值來判斷;
0:成功
1-255:失敗
$?:存儲上一條命令執行狀態返回值;
例如:
判斷用戶centos是否登錄系統:
who|grep "^centos\>"
echo $?:顯示上一個命令狀態返回值;
取得命令狀態返回值,0表示登錄,1-255表示沒登錄;
(2)測試表達式
test EXPRESSION
[ EXPRESSION ]
` EXPRESSION `
字符比較時,用雙中括號
注意:EXPRESSION兩端必須要有空白字符,否則語法錯誤;
bash腳本編程之測試類型:
數值測試
字符串測試
文件測試
數值測試:數值之間的比較
-eq:是否等于;例如:[ $num1 -eq $num2 ],test 2 -eq 3或[ 2 -eq 3 ]
-ne:是否不等于;
-gt:是否大于;
-ge:是否大于等于;
-lt:是否小于;
-le:是否小于等于;
例如:
]# test 2 -gt 3
]# [ $A -eq $B ]
練習:
centos用戶id是否比ubuntu用戶的ID大?
a=$(id -u centos)
b=$(id -u ubunto)
[ $a -gt $b ] && echo "centos is bigger" || echo "ubuntu is bigger"
字符串測試比較
==:是否等于,等于為真;
!=:是否不等,不等于為真;
>:是否大于,大于為真;
<:是否小于,小于為真;
=~:左側字符串是否能被右側的PATTERN所匹配(左邊是否包含右邊),包含為真;
例如:
]# name=uubuntuu
]# [[ "$naem" =~ buntu ]]
]# echo $?
練習:
判斷主機名是否包含magedu,如果不包含,則修改為magedu;
]# a=$(hostname)
]# [[ "$name" =~ magedu ]] || hostname magedu
字符串是否為空:
-z "STRING":判斷指定的字符串是否為空;
空則為真,不空則為假
-n "STRING":判斷指定的字符串是否不空;
不空則為真,空則為假;
注意:
(1)字符串比較要加引號,表示引用;即不做變量替換可用單引號,做變量替換要用雙引號;
(2)字符串比較時,要使用` `雙中括號;
練習:
主機名如果為空,或者為localhost.locadomain或者包含localhost或者包含linux則統統將其設為www.magedu.com
]# [ -z "$name" -o "$name"=="localhost.locadomain" -o "$name"=~"localhost" -o "$name"=~"linux" ] && hostname www.magedu.com
注意:使用-o邏輯時,字符測試比較符不能有空格且字符要用引號,不能用雙中括號。
文件測試:
存在性測試
-a FILE
-e FILE:文件的存在性測試,如果存在,則為真;
例如:
]# [ -e /etc/passwd ]
文件的存在性及文件類型測試:既能測試存在性又能測試類別
-b FILE:文件是否存在并且為塊設備文件;
-c FILE:文件是否存在并且為字符設備文件;
-d FILE:文件是否存在并且為目錄文件;
-f FILE:文件是否存在并且為普通文件;
-h FILE或-L FILE:文件是否存在并且為符號鏈接文件;
-p FILE:文件是否存在并且為命名管道文件;
-S FILE:文件是否存在并且為套接字文件;
例如:
]# [ -c /dev/null ]
文件權限測試:
-r FILE:文件是否存在并且對當前用戶可讀;
-w FILE:文件是否存在并且對當前用戶可寫;
-x FILE:文件是否存在并且對當前用戶可執行;
例如:
[ -w /etc/passwd ] && echo ok
特殊權限測試:
-u FILE:文件是否存在并且擁有SUID權限;
-g FILE:文件是否存在并且擁有SGID權限;
-k FILE:文件是否存在并且擁有sticky權限;
例如:
[ -u /usr/bin/passwd ]
文件是否有內容:
-s FILE:是否有內容,有則為真:
例如:
[ -s /etc/fstab ]
文件時間戳是否變化:
-N FILE:文件自從上一次讀操作之后,是否被改過;
文件從屬關系測試:
-O FILE:當前用戶是否為文件的屬主;
-G FILE:當前用戶是否為文件的屬組;
雙目測試:
FILE1 -ef FILE2:文件1與文件2是否指向同一個文件系統上相同inode的硬鏈接;
FILE1 -nt FILE2:文件1是否新于文件2;
FILE1 -ot FILE2:文件1是否舊于文件2;
組合測試條件:
邏輯運算:
第一種方式:
COMMAND1 && COMMAND2:與運算;
COMMAND1 || COMMAND2:或運算;
! COMMAND(取反)
例如:
[ -O FILE ] && [ -r FILE ]
第二種方式:
expresssion1 -a expression2
expresssion1 -o expression2
! expresssion或-not expresssion
例如:
[ -O FILE -a -x FILE ]:當前引用是否為file文件的屬主,且是否有執行權限;
練習:將當前主機名稱保存至hostName變量中;
主機名如果為空,或者為localhost.localdomain,則將其設置為www.magedu.com;
hostName=$(hostname)
[ -z "$hostName" -o "$hostName"=="localhost.localdomain" -o "$hostName"=="localhost" ] && hostname www.magedu.com
腳本的狀態返回值:
默認是腳本中執行的最后一條命令的狀態返回值;
自定義狀態退出狀態碼;
exit [n]:n為自己指定的狀態碼;
注意:shell進程遇到exit時,即會終止,因此,整個腳本執行即為結束;
所以,exit不能隨意添加使用,要使用條件測試判斷后,確定是否添加exit退出;
埋點調試;使用$?能判斷是在哪個位置退出的,定位問題常用;
練習:
腳本實現,一行命令實現,magedu用戶是否存在,不存在,則添加,如果存在,以狀態碼為5的方式退出;
id mageedu &> /dev/null || useradd mageedu && exit 5
或:id mageedu &> /dev/null && exit 5 || useradd mageedu
bash腳本編程之向腳本傳遞參數
位置參數變量
命令行:*.sh argu參數1 argu參數2 argu參數3...
腳本里引用方式:$1 $2 $3...
myscript.sh agru1 argu2...
引用方式:
$1:保存argu參數1;
$2:保存argu參數2;
...
${10},${11}...
輪替:
shift [n]:位置參數輪替,n為數字默認為1;表示為一次輪替前幾個參數;
$1,$2...就叫位置參數變量,每次運行腳本時,通過傳遞參數變化來改變這些參數變量的值;
例如:
my_shell.sh ubuntu centos linux
引用方式:
腳本中使用$1,$2,$3
useradd $1
useradd $2
useradd $3
解釋:$1=ubuntu,$2=centos,$3=linux
shift #:表示刪掉指定參數;把后面的參數往前補充;
練習:
腳本實現,通過命令傳遞兩個文本路徑給腳本,計算其空白行數(^$)之和;
a=$(egrep "^$" $1|wc -l)
b=$(egrep "^$" $2|wc -l)
sum=$[$a+$b]
echo "a is :$a"
echo "b is $b"
echo "sum is :$sum"
shift #:會自動踢出命令參數;
特殊變量:
$0:保存腳本文件路徑本身;
$#:保存腳本參數的個數;
$*:保存所有參數;把每個參數當做一個個獨立參數顯示;
$@:保存所有參數;把每個參數合在一起,當做一個字符串參數顯示;
$0:表示命令行給定腳本文件路徑;
例如:命令行]# bash /tmp/parameter_blanksum.sh
腳本內容:echo $0
顯示結果為:/tmp/parameter_blanksum.sh
如果命令行是:bash parameter_blanksum.sh
腳本內容不變,顯示結果為:parameter_blanksum.sh
$#:表示腳本參數的個數;
例如:
]# bash parameter_blanksum.sh /etc/init.d/functions /etc/init.d/network
腳本內容:echo $#
顯示結果為:2
$*:表示參數為多個字符串;
$@:表示參數為一個字符串;
例如:命令行]# bash parameter_blanksum.sh /etc/init.d/functions /etc/init.d/network
腳本內容:echo $*
echo $@
顯示結果為:/etc/init.d/functions /etc/init.d/network
/etc/init.d/functions /etc/init.d/network
但是$*表示為2個分開的路徑,而$@表示為一整行為一個字符串。
練習:
1、判斷/etc/passwd是否為文件,如果為文件,則輸出/etc/passwd is files,該路徑通過命令傳遞的方式傳入,當傳入的命令個數大于1,則報錯;
[ $# -gt 1 ] && echo "something wrong " && exit 100
[ -f $1 ] && echo "/etc/passwd is files"
2、在root目錄下,寫腳本,可以一次性添加3名用戶,通過傳遞參數的方式,進行用戶添加,當傳遞的參數不符合3個的時候,報錯;
當三名用戶添加完成后,需要將腳本進行權限加固(鎖機制,不能再執行),將腳本權限改成屬主可讀可寫可執行;
! [ $# -eq 3 ] && echo "please give me three username" && exit 1
useradd $1 && a=1
useradd $2 && b=1
useradd $3 && c=1
sum=$[$a+$b+$c]
[ $sum -eq 3 ] && echo "$1 $2 $3" ||exit 2
chmod 700 $0
echo $1 is added
echo $2 is added
echo $3 is added
echo "this scripts mode is 700"
或:
[ $# -gt 3 ] || [ $# -lt 3 ] && echo "something wrong,give three user" && exit 1
id $1 &> /dev/null && echo "$1 exist" || useradd $1
id $2 &> /dev/null && echo "$2 exist" || useradd $2
id $3 &> /dev/null && echo "$3 exist" || useradd $3
echo "three users $1,$2,$3 are added success"
chmod 700 $0
echo "this script mode is 700"
過程式編程語言的代碼執行順序:
順序執行:逐條運行;
選擇執行:
代碼存在一個分支:條件滿足時才會執行;
兩個或以上的分支:只會執行其中一個滿足條件的分支;
循環執行:
某代碼片段(循環體)要執行0、1或多個來回(循環);
順序執行
條件選擇:
(1)&&,||
(2)if語句(單分支,雙分支,多分支)
(3)case語句
循環執行:for,while,until
bash腳本編程之if語句選擇分支
條件判斷:
shell執行是順序執行的
選擇分支
單分支的if語句
if 測試條件;then(如果條件為真,則執行下面語句)
代碼分支
fi
或
if 測試條件
then
代碼分支
fi
雙分支的if語句
if 測試條件;then
條件為真時執行的分支
else
條件為假時執行的分支
fi
多分支的if語句
if 測試條件;then
條件為真時候執行的分支
elif 測試條件;then
elif 測試條件;then
elif 測試條件;then
else
條件為假時候執行的分支
fi
例如:
通過參數傳遞給一個用戶名給腳本,此用戶如果不存在則創建用戶;
if ! grep "^$1\>" /etc/passwd &> /dev/null;then
useradd $1
echo $1|passwd --stdin $1 &> /dev/null
echo "add a user $1 finished"
else
echo "$1 is exist"
fi
示例:通過參數傳遞一個用戶名給腳本,此用戶不存在時,則添加;(判斷表達方法:一種是命令,另一種是表達式放在中括號中或用test表示,判斷用戶是否存在用id或grep)
~]# vim useradd.sh
if ! grep "^$1\>" /etc/passwd &> /dev/null;then
useradd $1
echo $1 | passwd --stdin $1 &> /dev/null
echo "add user $1 finished"
fi
~]# ./useradd.sh hbase
執行結果為:add user hbase finished
加入了判斷參數是否存在的判斷if語句:
~]# vim useradd.sh
if [ $# -lt 1 ];then
echo "at least one username"
exit 2
fi
if ! grep "^$1\>" /etc/passwd &> /dev/null;then
useradd $1
echo $1 | passwd --stdin $1 &> /dev/null
echo "add user $1 finished"
fi
~]# ./useradd.sh hbase
變為雙分支判斷if語句:
~]# vim useradd.sh
if [ $# -lt 1 ];then
echo "at least one username"
exit 2
fi
if grep "^$1\>" /etc/passwd &> /dev/null;then
echo "user $1 exists"
else
useradd $1
echo $1 | passwd --stdin $1 &> /dev/null
echo "add user $1 finished"
fi
~]# ./useradd.sh hbase
練習:
1、通過命令參數,給定兩個數字,輸出其中較大的數值;
if [ $1 -gt $2 ] ;then
echo $1 is bigger
else
echo $2 is bigger
fi
或:
if [ ! $# -eq 2 ];then
echo "give two number"
exit 2;
fi
if [ $1 -gt $2 ];then
echo "the bigg is $1"
elif [ $1 -lt $2 ];then
echo "the bigg is $2"
else
echo "$1=$2"
fi
2、通過命令參數,給定兩個文本文件名,如果某文件不存在,則結束腳本,如果都存在返回每個文件的行數,并echo其中行數較多的文件名;
[ $# -ne 2 ] && echo "give two file" && exit 1
if ! [ -e $1 ];then
echo "$1 is not find"
exit 2
elif ! [ -e $2 ];then
echo "$2 is not find"
exit 3
fi
f1=$(cat $1|wc -l)
f2=$(cat $2|wc -l)
echo "$1 line is $f1"
echo "$2 line is $f2"
if [ $f1 -gt $f2 ];then
ehco "$f1 is more"
elif [ $f1 -eq $f2 ];then
echo "f1 and f2 the same"
else
echo "$f2 is more"
fi
或:
if ! [ $# -eq 2 ];then
echo "give 2 files"
exit 20;
elif ! [ -e $1 ];then
echo "$1 is not exist"
elif ! [ -e $2 ];then
echo " $2 is not exist"
exit 30;
else
f1=$(cat $1|wc -l)
f2=$(cat $2|wc -l)
echo "f1=$f1"
echo "f2=$f2"
fi
if [ $f1 -gt $f2 ];then
echo "file1 line is more :$f1"
echo "$(basename $1) line is more"
elif [ $f1 -lt $f2 ];then
echo "file2 line is more :$f2"
echo "$(basename $2) line is more"
else
echo "f1-$f1 and f2-$f2 is same"
fi
3、通過命令行參數給定一個用戶名,判斷ID號是偶數還是奇數;
多分支if語句
選擇執行:
(1)&&,||
(2)if語句
(3)case語句
if語句:三種格式
單分支的if語句
if CONDITION;then
if-ture-分支
fi
雙分支的if語句
if CONDITION;then
if-true-分支
else
if-false-分支
fi
多分支的if語句
if CONDITION1;then
條件1為真分支
elif CONDITION2;then
條件2為真分支
elif CONDITION3;then
條件3為真分支
...
elif CONDITIONn;then
條件nweizhen分支
else
所有條件均不滿足時的分支
fi
注意:即便多個條件可能同時滿足,分支只會執行其中一個,首先測試為真的條件分支執行后,就退出;
示例:通過腳本參數,傳遞文件路徑給腳本,判斷此文件的類型;
if [ $# -lt 1 ];then
echo "at lease one path"
exit 1
fi
if ! [ -e $1 ];then
echo "no such file"
eixt 2
fi
if [ -f $1 ];then
echo "common file"
elif [ -d $1 ];then
echo "directory file"
elif [ -L $1 ];then
echo "symbolic link file"
elif [ -b $1 ];then
echo "block special file"
elif [ -p $1 ];then
echo "pipe file"
elif [ -S $1 ];then
echo "socket file"
elif [ -c $1 ];then
echo "character special file"
case語句其實就是簡化版的多分支if語句,但未所有if語句都能轉化為case語句;
注意:if語句可以嵌套使用;
練習:
1、寫腳本實現如下功能:
(1)傳遞參數給腳本,此參數為用戶名;
(2)根據其id來判斷用戶類型;
0:管理員
1-999:系統用戶
1000+:登錄用戶
(3)如不存在,可添加此用戶;
[ $# -lt 1 ] && echo "at least one username" && exit 1
! id $1 &> /dev/null && echo "no such user" && exit 2
userid=$(id -u $1)
if [ $userid -eq 0 ];then
echo "root"
elif [ $userid -ge 1000 ];then
echo "login user"
else
echo "system user"
fi
2、寫腳本實現如下功能:
(1)列出如下菜單給用戶
disk)show disks info(fdisk -l /dev/sda)
mem)show memory info(free -m)
cpu)show cpu info(使用cat /proc/cpuinfo或lscpu)
*)quit
(2)提示用戶給出自己的選擇,然后顯示對應其選擇的相應系統信息;
cat << EOF
disk) show disk info
mem) show memory info
cpu) show cpu info
*) QUIT
EOF
read -p "please choice : " option
if [[ "$option" == "disk" ]];then
fdisk -l /dev/[sh]d[a-z]
elif [[ "$option" == "mem" ]];then
free -m
elif [[ "$option" == "cpu" ]];then
lscpu
else
echo "your choice fault option"
fi
bash腳本編程之for循環
循環執行:將一段代碼重復執行0、1或多次;
進入循環條件:只有條件滿足時才進入循環;
退出循環條件:每個循環都應該有退出條件,有機會退出循環;
bash腳本有三種循環方式:
for循環
while循環
until循環
for循環有2種格式:
(1)遍歷列表
(2)控制變量
變量賦值有三種方式:
(1)VAR=VALUE,直接賦值;
(2)通過read,實現變量賦值;
(3)for循環中,用列表賦值給變量;
遍歷列表:
for VARAIBLE in LIST;do
循環體
done
進入條件:只有列表有元素,即可進入循環;
退出條件:列表中的元素遍歷完成;
LIST的生成方式:
(1)直接給出;
(2)整數列表;
(a){start..end}自動展開;
(b)seq #:從1列出到#的數字;
seq start end:從開始數字列出到結束數字;
seq start step end:從開始數字,根據步長,列出結束數字;
seq命令:顯示一系列數字
seq [start [increment]] last
(3)能返回一個列表的命令;
例如:ls命令
(4)glob通配符機制;
例如:/etc/p*
(5)變量引用
例如:$@,$*
...
例如:
seq 10:列出1 2 3 4 5 6 7 8 9 10
seq 5 10:列出5 6 7 8 9 10
seq 1 2 10:列出1 3 5 7 9
seq 2 2 10:列出2 4 6 8 10
例如:循環添加三個用戶aaa,bbb,ccc;
for username in aaa bbb ccc;do
if id $username &>/dev/null;then
echo "$username exists"
esle
useradd $username && echo "add user $username finished"
fi
done
例如:在tmp目錄下創建10個文件f1-10;
for filename in {1..10};do
touch /tmp/f$filename
done
注意:在如何時候,要考慮判斷條件;如上例,應該先判斷文件是否存在;
例如:求100內所有正整數之和;
declare -i sum=0
for i in {1..100};do
sum=$[$sum+$i]
done
echo $sum
例如:計算100內的奇數和;
declare -i sum=0
for i in $(seq 1 2 100);do
sum=$[$sum+$i]
done
echo $sum
例如:計算100內的偶數和;
declare -i sum=0
for i in $(seq 2 2 100);do
sum=$[$sum+$i]
done
echo $sum
示例:判斷/var/log目錄下的每個文件的類型;
file /var/log/*即可判斷,但要求用循環實現;
for filename in /var/log/*;do
if [ -f $filename ];then
echo "this is common file"
elif [ -d $filename ];then
echo "this is directory file"
elif [ -L $filename ];then
echo "this is softlink"
elif [ -b $filename ];then
echo "this is block file"
elif [ -c $filename ];then
echo "this is character file"
elif [ -S $filename ];then
echo "this is socket file"
elif [ -p $filename ];then
echo "thisi is pipe file"
else
echo "unknow file type"
fi
done
練習:
1、分別求100內偶數之和,奇數之和;
計算100內的奇數和;
declare -i sum=0
for i in $(seq 1 2 100);do
sum=$[$sum+$i]
done
echo $sum
計算100內的偶數和;
declare -i sum=0
for i in $(seq 2 2 100);do
sum=$[$sum+$i]
done
echo $sum
2、計算當前系統上所有用戶的id之和;
declare -i sum=0
for i in $(cut -d: -f3 /etc/passwd);do
echo "\$sum=$sum,\$i=$i"
sum=$[$sum+$i]
done
echo $sum
3、通過腳本參數傳遞一個目錄給腳本,然后計算此目錄下所有文本文件的行數之和;并說明此類文件的總數;
! [ $# -eq 1 ] && exit 1
! [ -d $1 ] && echo "please give a comment file" && exit 2
declare -i filesum=0
declare -i sum=0
declare -i A=0
for i in $(ls $1);do
if [ -f $i ];then
a=$(wc -l $i|cut -d" " -f1)
A+=1
sum=$[$sum+$a]
echo "file line sum=$a,files sum $A,all file line sum is $sum"
else
echo "this file not text type"
fi
done
for循環格式:
for VARAIBLE in LIST;do
循環體
done
LIST列表生成方式有多種:直接給出,{#..#},或glob(/tpm/test/*)等任何能夠返回列表的命令都可以;
for循環的特殊用法:
for ((控制變量初始化;條件判斷表達式;控制變量的修正語句)); do
循環體
done
注意:條件判斷可直接使用<,>;
控制變量初始化:僅在循環代碼開始運行時執行一次;
控制變量修正語句:會在每輪循環結束會先進行控制變量修正運算,而后再做條件判斷;
示例:求100內整正數之和
declare -i sum=0
for ((i=1;i<=100;i++));do
let sum+=$i
done
echo "sum: $sum"
練習:打印99乘法表
for ((j=1;j<=9;j++));do
for ((i=1;i<=j;i++));do
echo -e -n "${i}X${j}=$[${i}*${j}]\t"
done
echo
done
bash腳本編程之while循環和until循環
while循環:
while CONDITION;do
循環體
循環擴展變量修正表達式(條件修正表達式)
done
進入條件:CONDITION參數為“真”;
退出條件:CONDITION參數為“假”;
until循環:
until CONDITION;do
循環體
循環擴展變量修正表達式(條件修正表達式)
done
進入條件:CONDITION參數為“假”;
退出條件:CONDITION參數為“真”;
until就相當于在while條件前取反(!)的效果;
例如:求100內正整數和;
比for優勢在于,如果數值比較多,for的列表會占用內存,while則使用的是變量,占用內存空間很小;
for循環:
for i in {1..100};do
sum=$[$sum+$i]
done
echo $sum
while循環:
declare -i sum=0
declare -i i=1
while [ $i -le 10 ];do
sum=$[$sum+$i]
let i++
done
echo $sum
until循環:
declare -i sum=0
declare -i i=1
until [ $i -gt 100 ];do
let sum+=$i
let i++
done
echo $sum
練習:分別使用for、while、until各自實現;
1、求100內所有偶數之和、奇數之和;
2、創建10個用戶,分別為user101-user110;密碼同用戶名;
3、打印九九乘法表;
4、打印逆序九九乘法表;
1x1=1
1x2=2,2x2=4
1x3=3,2x3=6,3x3=9
循環嵌套:
外循環控制乘數,內循環控制被乘數;
for j in {1..9};do
for i in $(seq 1 $j);do
echo -n -e "${i}x${j}=$[${i}*${j}]\t"
done
echo
done
進入條件:
for:列表元素非空;
while:條件測試結果為真;
until:條件測試結果為假;
退出條件:
for:列表元素遍歷完成;
while:條件測試結果為假;
until:條件測試結果為真;
循環控制語句:
continue:提前結束本輪循環,而直接進入下一輪循環判斷;
while CONDITION1; do
CMD1
...
if CONDITIONS2; then
continue
fi
CMDn
...
done
示例:求100內偶數和;
declare -i evensum=0
declare -i i=0
while [ $i -le 100 ];do
let i++
if [ $[$i%2] -eq 1 ];then
continue
fi
let evensum+=$i
done
echo "evensum : $evensum"
break:提前跳出循環
while CONDITION1; do
CMD1
...
if CONDITIONS2; then
break
fi
done
break #:在循環嵌套時,指明跳出哪層循環;
sleep命令:
delay for a specified amount of time
sleep #
#:為數字默認單位是s秒鐘,可用m分鐘,h小時,d天為單位;
創建死循環:
while true;do
循環體
done
退出方式:
某個測試條件滿足時,讓循環體執行break命令;
until false;do
循環體
done
示例:求100內奇數和
declare -i oddsum=0
declare -i i=1
while true; do
let oddsum+=$i
let i+=2
if [ $i -gt 100 ];then
break
fi
done
練習:每隔3秒鐘到系統上獲取已經登錄的用戶的用戶信息,其中,如果aaa用戶登錄系統,則記錄于日志中,并退出;
while true;do
if who | grep "^aaa\>" &>/dev/null;then
break
fi
sleep 3
done
echo "$(date +"%F %T")aaa loggend on " >> /tmp/users.log
或改寫為:
until who |grep "^aaa\>" &>/dev/null;do
sleep 3
done
echo "$(date +"%F %T") aaa longed on" >> /tmp/user.log
while循環的特殊用法(遍歷文件的行):
while read VARIABLE;do
循環體;
done < /PATH/FROM/SOMEFILE
依次讀取/PATH/FROM/SOMEFILE文件中的每一行,且將其值賦值給VARIABLE變量;
示例:找出ID號為偶數的用戶,顯示器用戶名、ID及默認shell;
while read line;do
userid=$(echo $line |cut -d: -f3)
username=$(echo $line |cut -d: -f1)
usershell=$(echo $line |cut -d: -f7)
if [ $[$userid%2] -eq 0 ];then
echo "$username,$userid,$usershell"
fi
done < /etc/passwd
bash腳本編程之用戶交互
腳本參數
用戶交互:通過鍵盤輸入數據
read [OPTION] ... [name ...]
-p 'PROMPT':輸出提示符,設置提示信息;;
-t TIMEOUT:設定超時退出時間;
name:是在腳本里設定的變量;
bash -n /path/to/some_script
檢測腳本中的語法錯誤
bash -x /path/to/some_script
調試執行
例如:腳本實現,使用read添加用戶,密碼同用戶名;
read -p "plase give a username and passwd: " a b
useradd $a
echo $a
echo $b
echo "$b "| passwd $a --stdin &> /dev/null
例如:腳本交互,輸入指定用戶名,創建用戶并設定密碼;
read -p "Enter a username: " name
[ -z "$name" ] && ehco " a username is needed " && exit 2
read -p "enter a passwd for $name,[passwd]: " passwd
[ -z "$passwd" ] && passwd="passwd"
if id $name &> /dev/null;then
echo "$name exists"
else
useradd $name
echo "$passwd " | passwd --stdin $name &> /dev/null
echo "add user $name finished"
fi
練習:
1、判斷給定是兩個數值,誰大誰小
給定數值的方法;命令參數,命令交互
read -p "give two number: " a b
! [ $# -eq 2 ] && echo "give two number" && exit 1
if [ $a -gt $b ];then
echo " $a is bigger"
else
echo "$b is bigger"
fi
2、提示用戶輸入一個字符串,如果輸入的是quit,則退出,否則顯示其輸入的字符串內容;
read -p "give a string: " a
if ! [ $a == quit ];then
echo "show string is $a"
else
exit 1
fi
3、背景:公司新員工,要開通系統賬號和統計出新員工的信息(通過交互方式);
讓用戶指定一個用戶名和密碼,如果用戶名之前存在,先進行刪除,之后則為用戶添加系統賬號;
完成后,需要統計員工的手機號,email,qq,年齡,收集后存儲到該用戶的家目錄中;
以上完成后,詢問該用戶是否需要給用戶單獨創建一個工作目錄為/data/username,默認是需要,如果不需要,則輸入n或N;
read -p "input a password for useradd: " password
echo $password|passwd --stdin $username &> /dev/null
echo "user's password is add finished"
read -p "input personal infomation:tel): " tel
echo "$username tel:$tel" >> /home/$username/userinfo.txt
read -p "input personal infomation:email): " email
echo "$username email:$email" >> /home/$username/userinfo.txt
read -p "input personal infomation:qq): " qq
echo "$username qq:$qq" >> /home/$username/userinfo.txt
read -p "input personal infomation:age): " age
echo "$username age:$age" >> /home/$username/userinfo.txt
read -p "you if create personal work DIR (y/n,N)default: y: " dir
if [ "$dir" == "y" ];then
mkdir -p /data/$username
elif [ "$dir" == "n" -o "$dir" == "N" ];then
echo "you choice nocreate personal work DIR,bye"
exit 2
fi
bash腳本編程之條件選擇case語句
case語句:
if中的多分支in語句:
if CONDITION1;then
分支1(CONDITION1為真時執行的語句)
elif CONDITION2;then
分支2(CONDITION2為真時執行的語句)
...
else CONDITION;then
分支n
fi
示例:顯示菜單給用戶,
disk) show disk info
mem) show memory info
cpu) show cpu info
*) QUIT
要求:(1)提示用戶給出自己的選擇;
(2)正確的選擇給出相應信息,否則,提示重新選擇正確的選項;
cat << EOF
cpu) display cpu information
mem) display memory infomation
disk) display disks infomation
quit) quit
==============================
EOF
read -p "Enter your choice: " option
while [ "$option" != "cpu" -a "$option" != "mem" -a "$option" != "disk" -a "$option" != "quit" ];do
echo "cpu,mem,disk,quit,please see "
read -p "Enter your choice again: " option
if [ "$option" == "cpu" ];then
lscpu
elif [ "$option" == "mem" ];then
free -m
elif [ "$option" == "disk" ];then
fdisk -l /dev/[hs]d[a-z]
else
echo "quit"
exit 0
fi
done
注意:用一個變量與多個值比較時,就可使用case語句替換成if多分支語句;
case語句的語法格式:
case $VARAIBLE in
PAT1)
分支1
;;
PAT2)
分支2
;;
...
*)
分支n
;;
esac
表示變量VARAIBLE與多個模式比較PAT1、PAT2、...,每個分支用雙分號結尾,多分支執行特點是只要第一個滿足條件,就不執行下面的語句,即使下面的條件也滿足也不執行了;雙分號可單獨成行,也可在分支語句的最后;PAT只支持glob風格的通配符;僅適用于一般都是變量與PAT做等值比較或模式匹配比較時,可替代if多分支語句;
示例:上例用case實現
cat << EOF
cpu) display cpu information
mem) display memory infomation
disk) display disks infomation
quit) quit
==============================
EOF
read -p "Enter your choice: " option
while [ "$option" != "cpu" -a "$option" != "mem" -a "$option" != "disk" -a "$option" != "quit" ];do
echo "cpu,mem,disk,quit,please see "
read -p "Enter your choice again: " option
done
case $option in
cpu)
lscpu;;
mem)
free -m;;
disk)
fdisk -l /dev/[hs]d[a-z];;
*)
echo "quit"
exit 0;;
esac
case支持glog風格的通配符:
*:任意長度的任意字符;
?:任意單個字符;
[]:范圍內任意單個字符;
a|b:a或b;
示例:腳本實現服務框架(服務腳本不能以.sh結尾)
$lockfile,/var/lock/subsys/SCRIPT_NAME
(1)此腳本可接受start,stop,restart,status四參數之一;
(2)如果參數非此四參數,則提示使用幫助后退出;
(3)start則創建lockfile,并顯示啟動;stop則輸出lockfile,并顯示停止;restart則先刪除此文件再創建文件,然后顯示重啟完成;status則如果lockfile操作則顯示running,否則顯示為stopped;
# 下面2語句為服務腳本格式
# chkconfig: - 50 50
# description: test service script
#
prog=$(basename $0)
lockfile=/var/lock/subsys/$prog
case $1 in
start)
if [ -f $lockfile ];then
echo "$prog is running yet"
else
touch $lockfile
[ $? -eq 0 ] && echo "start $prog finished"
fi
;;
stop)
if [ $lockfile ];then
rm -f $lockfile
[ $? -eq 0 ] && echo "stop $prog finished"
else
echo "$prog is not running"
fi
;;
restart)
if [ -f $lockfile ];then
rm -f $lcokfile
touch $lockfile
echo "restart $prog finished"
else
touch -f $lockfile
echo "start $prog finished"
fi
;;
status)
if [ -e $lockfile ];then
echo "$prog is running"
else
echo "$prog is stopped"
fi
;;
*)
echo "Usage :$prog {start|stop|restart|status}"
exit 1
esac
]# cp exercise.sh /etc/init.d/exercise
]# chkconfig --add exercise
]# chkconfig --list exercise
]# chmod +x /etc/init.d/exercise
服務運行以后都會在/var/lock/subsys/下,創建一個鎖文件,名稱同服務名稱;
bash腳本編程之函數:function(功能)
在過程式編程里:實現代碼重用
模塊化編程
結構化編程
把一段獨立功能的代碼當做一個整體,并為之取一個名字;命名的代碼段,此即為函數;
注意:定義函數的代碼段不會自動執行,在調用時才執行;所謂調用函數,在代碼中給定函數名即可;
函數名出現的任何位置,在代碼執行時,都會被自動替換為函數代碼;
語法一:
function function_name {
...函數體
}
語法二:
function_name() {
...函數體
}
函數的生命周期:每次被調用時創建,返回時終止;函數也有狀態返回值,其狀態返回值默認為函數體中運行的最后一條命令的狀態結果;就像腳本中使用exit #表現為自定義退出狀態結果;而函數用return表示自定義狀態返回值;
return [0-255]
0:成功;
1-255:失敗;
示例:給定一(或二)個用戶名,取得用戶的ID號和默認shell;
沒用函數:
if id "$1" &> /dev/null;then
grep "^$1\>" /etc/passwd|cut -d: -f3,7
else
echo "no such user"
fi
使用函數:
userinfo() {
if id "$username" &> /dev/null;then
grep "^$username\>" /etc/passwd|cut -d: -f3,7
else
echo "no such user"
fi
}
username=$1
userinfo
username=$2
userinfo
示例:改寫腳本服務框架(服務腳本不能以.sh結尾)
# chkconfig: - 50 50
# description: test service script
#
prog=$(basename $0)
lockfile=/var/lock/subsys/$prog
start() {
if [ -f $lockfile ];then
echo "$prog is running yet"
else
touch $lockfile
lockfile=/var/lock/subsys/$prog
start() {
if [ -f $lockfile ];then
echo "$prog is running yet"
else
touch $lockfile
[ $? -eq 0 ] && echo "start $prog finished"
fi
}
stop() {
if [ $lockfile ];then
rm -f $lockfile
[ $? -eq 0 ] && echo "stop $prog finished"
else
echo "$prog is not running"
fi
}
restart() {
if [ -f $lockfile ];then
rm -f $lcokfile
touch $lockfile
echo "restart $prog finished"
else
touch -f $lockfile
echo "start $prog finished"
fi
}
status() {
if [ -e $lockfile ];then
echo "$prog is running"
else
echo "$prog is stopped"
fi
}
usage() {
echo "Usage :$prog {start|stop|restart|status}"
exit 1
}
case $1 in
start)
start ;;
stop)
stop;;
restart)
stop
start ;;
status)
suatus;;
*)
usage
exit 1;;
esac
函數返回值:
函數的執行結果返回值:
(1)使用echo或printf命令進行輸出;
(2)函數體中調用的命令的執行結果;
函數的退出狀態碼:
(1)默認取決于函數體中執行的最后一條命令的退出狀態碼;
(2)自定義:使用return #
命令都有兩種返回值,一種為執行結果返回值,一種為執行的狀態結果返回值稱為退出狀態碼;在腳本中實現命令替換,命令替換的地方就調用命令執行的結果,函數也一樣,函數替換是調用函數出現的地方,就是函數代碼執行的結果;printf命令:不能換行,但輸出可定義格式化輸出;在awk命令時再介紹;
函數可以接受參數:
傳遞參數給函數:
在函數體中,可以使用$1, $2, ...引用傳遞給函數的參數;還可在函數中使用$*或$@引用所有參數,$#引用傳遞的參數的個數;
在調用函數時,在函數名后面以空白符分隔給定參數列表即可,例如,testfunction arg1 agr2 agr3 ...
示例:添加10個用戶,使用函數實現,用戶名作為參數傳遞給函數;
]# bash -x exercise.sh aaa
addusers() {
if id $1 &> /dev/null;then
return 5
else
useradd $1
retval=$?
return $retval
fi
}
for i in {1..10};do
addusers ${1}${i}
retval=$?
if [ $retval -eq 0 ];then
echo "add user ${1}${i} finished"
elif [ $retval -eq 5 ];then
echo "user ${1}${i} exist"
else
echo "unkown error"
fi
done
練習:
1、腳本函數實現ping主機,來測試主機的在線狀態;
主程序:實現測試172.16.1.1-172.16.67.1范圍內個主機的在線狀態;
分別使用for,while和until循環實現;
普通方式:
declare -i uphosts=0
declare -i downhosts=0
for i in {1..67};do
if ping -W 1 -c 1 172.16.$i.1 &>/dev/null;then
echo "172.16.$i.i is up"
let uphosts+=1
else
echo "172.16.$i.1 is down"
let downhosts+=1
fi
done
echo "Up hosts: $uphosts,Down hosts: $downhosts"
改版2:函數+while循環方式:
declare -i uphosts=0
declare -i downhosts=0
declare -i i=1
hostping() {
if ping -W 1 -c 1 $1 &>/dev/null;then
echo "$i is up"
let uphosts+=1
else
echo "$1 is down"
let downhosts+=1
fi
}
while [ $i -le 67 ];do
hostping 172.16.$i.1
let i++
done
echo "Up hosts: $uphosts,Down hosts: $downhosts"
改版3:使用return,在主程序中統計ping的主機數量;
declare -i uphosts=0
declare -i downhosts=0
declare -i i=1
hostping() {
if ping -W 1 -c 1 $1 &>/dev/null;then
echo "$i is up"
return 0
else
echo "$1 is down"
return 1
fi
}
while [ $i -le 67 ];do
hostping 172.16.$i.1
[ $? -eq 0 ] && let uphosts++ || let downhosts++
let i++
done
echo "Up hosts: $uphosts,Down hosts: $downhosts"
2、腳本函數實現,打印nn乘法表;
變量作用域:
局部變量:作用域是函數的生命周期;在函數結束時被自動銷毀;
定義局部變量的方法:local VARIABLE=VALE
本地變量:作用域是運行腳本的shell進程生命周期;因此,其作用范圍就是當前shell腳本程序文件;
例如:
name=tom
setname() {
name=jerry
echo "Function: $name"
}
setnaem
echo "Shell: $name"
此時函數的name和shell的name都是jerry,變量name為本地變量,可在函數中調用;
顯示結果為:
Function:jerry
Shell: jerry
如果在以上語句中改為:local name=jerry
顯示結果為:
Function:jerry
Shell: tom
bash腳本編程之函數遞歸:
函數直接或間接調用自身;
做階乘或斐波那契數列時會用到函數遞歸;
例如:10!=10*9!=10*9*8!=10*9*8*7!...=10*9*8*7*6*5*4*3*2*1!
函數階乘 傳遞參數為n
n*(n-1)!=n*(n-1)*(n-2)!=...
例如:斐波那契數列
每一個數值都是前2個數的和;求第n階的數是多少就用函數;
1 1 2 3 5 8 13 21 ...
例如:傳遞參數為一個數字,實現數字的階乘;
fact() {
if [ $1 -eq 0 -o $1 -eq 1 ];then
echo 1
else
echo $[$1*$(fact $[$1-1])]
fi
}
fact $1
例如:傳遞參數為一個數字,實現斐波那契數列
fab() {
if [ $1 -eq 1 ];then
echo -n "1 "
elif [ $1 -eq 2 ];then
echo -n "1 "
else
echo -n "$[$(fab $[$1-1])+$(fab $[$1-2])] "
fi
}
for i in $(seq 1 $1);do
fab $i
done
ech
bash腳本編程之數組
函數、case語句
case語句語法格式:
case $VARIABLE in
PAT1)
分支1
;;
PAT2)
分支2
;;
...
*)
分支n
;;
esac
PATTERN:為Glob通配符;
函數:能完成結構化編程,實現代碼重用;
function function_name{
函數體
}
function_name() {
函數體
}
函數定義后,被調用才能執行;
函數調用:給定函數名即可調用,也可給函數傳遞參數,使用位置參數$1,$2,...
局部變量:local VARIABLE
生命周期為調用函數時,函數體內;
數組:
程序=指令+數據
bash腳本中指令:就是整個編程中的語法關鍵字,加上各種系統上的命令,加上定義的函數;
數據:存在變量、文件中、或數據組織結構中;
變量:存儲單個元素的內存空間;
數組:存儲多個元素的連續的內存空間;
數組名:整個數組只有一個名字;數組在第一個內存空間中存儲的數字就是數組名指向的位置;
數組索引:編號從0開始;引用數組種的數據方式:
數組名[索引]
${ARRAY_NAME[INDEX]}
注意:bash-4及之后的版本,支持自定義索引格式,而不僅是0,1,2,...數字格式;
自定義索引格式的屬組,稱為關聯數組;
聲明數組:
declare -a NAME:聲明索引數組;
declare -A NAME:聲明關聯數組;
查看聲明幫助:
]# help declare
數組中的數據就是存在內存中的一段連續的內存空間,分別存儲多個連續的數據,每個數據都是獨立的被管理單元,只不過沒有獨立的名稱,要通過數組名稱來索引使用,數組名稱加上一個下標可理解為數據在數組中的標識符;
數組中元素的賦值方式:
(1)一次只賦值一個元素:
ARRAY_NAME[INDEX]=value
例如:
]# animals[0]=pig
]# animals[1]=cat
]# echo $animals
]# echo ${animals[1]}
(2)一次賦值全部元素:
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)
會自動賦值為0,1,2,3,...
例如:
]# weekdays=("Monday" "Tuseday" "Wednesday")
]# echo ${weekdays[2]}
(3)只賦值特定元素;
ARRAY_NAME=([0]="VAL1" [3]="VAL4" ...)
為稀疏格式的數組;
注意:bash支持稀疏格式的數組;
例如:
]# sword=([0]="Yitian" [3]="Tulong")
]# echo ${sword[1]}:顯示為空;
]# echo ${sword[3]}:顯示為Tulong;
(4)read -a ARRAY_NAME
例如:
]# read -a Linux
sed awk find grep
]# echo ${Linux[1]}:顯示為awk;
關聯數組:
]# declare -A world:聲明關聯數組;
]# world[us]"America"
]# echo "${world[us]}":顯示為America;
]# world[uk]"unite kingdom"
]# echo "${world[uk]}":顯示為unite kingdom;
引用數組中的元素:${ARRAY_NAME[INDEX]}
注意:引用時,只給數組名,表示引用下標為0的元素;
${ARRAY_NAME[*]}:引用數組中的所有元素;
${ARRAY_NAME[@]}:同上;
數組的長度(數組中元素的個數):
${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}
例如:
]# echo "${#Linux[*]}:顯示數組元素個數4個;
]# echo "${#animals[@]}:顯示數組元素個數2個;
]# echo "${#animals}:顯示數組第一個元素的字符個數為3個;
]# echo "${animals[*]}:顯示數組中所有元素;可生成列表;
示例:生成10隨機數,并找出其中最大值和最小值;
declare -a rand
declare -i max=0
for i in {0..9};do
rand[$i]=$RANDOM
echo ${rand[$i]}
[ ${rand[$i]} -gt $max ] && max=${rand[$i]}
done
echo "Max: $max"
練習:
1、生成10個隨機數,由小到達排序;
2、定義數組,數組中的元素是/var/log目錄下所有以.log結尾的文件;統計其下標為偶數的文件中的行數之和;
declare -a files
files=(/var/log/*.log)
declare -i lines=0
for i in $(seq 0 $[${#files[*]}-1]);do
if [ $[$i%2] -eq 0 ];then
let lines+=$(wc -l ${files[$i]} |cut -d' ' -f1)
fi
done
echo "lines: $lines"
引用數組中的所有元素:
${ARRAY_NAME[*]}
${ARRAY_NAME[@]}
也可只返回有限的幾個元素;
數組元素切片:${ARRAY_NAME[@]:offset:number}
offset:元素偏移量;
number:取元素的個數;省略number時,表示取偏移量之后的所有元素;
例如:
]# files=(/etc/[Pp]*)
]# echo ${files[*]}
顯示結果:
/etc/pam.d /etc/passwd /etc/passwd- /etc/pinforc /etc/pkcs11 /etc/pki /etc/plymouth /etc/pm /etc/polkit-1 /etc/popt.d /etc/postfix /etc/ppp /etc/prelink.conf.d /etc/printcap /etc/profile /etc/profile.d /etc/protocols /etc/pulse
]# echo ${files[@]:2:3}:表示從上面的數組中跳過2個,取3個;
顯示結果:
/etc/passwd- /etc/pinforc /etc/pkcs11
]# echo ${files[@]:5}
顯示結果:
/etc/pki /etc/plymouth /etc/pm /etc/polkit-1 /etc/popt.d /etc/postfix /etc/ppp /etc/prelink.conf.d /etc/printcap /etc/profile /etc/profile.d /etc/protocols /etc/pulse
向非稀疏格式的數組中追加元素:
ARRAY_NAME[${#ARRAY_NAME[*]}]=
${#ARRAY_NAME[*]}]:表示取出數組中的元素個數;
刪除數組中的某元素:
unset ARRAY[INDEX]
關聯數組:
declare -A ARRAY_NAME
ARRAY_NAME=([index_name1]="value1" [index_name2]="value2" ...)
bash腳本編程之bash的內置字符串處理工具:
字符串切片:(基于位置取字串)
${var:offset:number}:取字符串的子串;
${var: -length}:取字符串的最右側的幾個字符;
注意:冒號后必須有一個空格;
例如:
]# name=jerry
]# echo ${name:2}
顯示結果:rry
]# echo ${name:2:2}
顯示結果:rr
]# echo ${name: -4}
顯示結果:erry
基于模式取字串:
${var#*word}:
其中word是只讀的分隔符;功能:從左向右,查找var變量所存儲的字符串中,第一次出現的word分隔符,刪除字符串開頭至此分隔符之間的所有字符;
${var##*word}:(可用于取路徑基名、端口號)
其中word是只讀的分隔符;功能:從左向右,查找var變量所存儲的字符串中,最后一次出現的word分隔符,刪除字符串開頭至此分隔符之間的所有字符;
例如:
]# mypath="/etc/init.d/functions"
]# echo ${mypath#*/}:顯示內容:etc/init.d/functions;
]# echo ${mypath##*/}:顯示內容:functions;(即取路徑基名)
${var%word*}:(可用于取路徑名)
其中word是只讀的分隔符;功能:從右向左,查找var變量所存儲的字符串中,第一次出現的word分隔符,刪除此分隔符至字符串尾部之間的所有字符;
${var%word*}:
其中word是只讀的分隔符;功能:從右向左,查找var變量所存儲的字符串中,最后一次出現的word分隔符,刪除此分隔符至字符串尾部之間的所有字符;
例如:
]# echo ${mypath%/*}:顯示內容:/etc/init.d;(即取路徑名)
]# echo ${mypath%%/*}:顯示內容:為空;
]# mypath="etc/init.d/functions"
]# echo ${mypath%%/*}:顯示內容:etc;
]# url="http://www.magedu.com:80"
]# echo ${url##*:}:顯示內容:80;(取端口號)
]# echo ${url%%:*}:顯示內容:http;
查找替換:
${var/PATTERN/SUBSTI}:
查找var所表示的字符串中,第一次被PATTERN所匹配到的字符串,并將其替換為SUBSTI所表示的字符串;
${var//PATTERN/SUBSTI}:
查找var所表示的字符串中,所有被PATTERN所匹配到的字符串,并將其全部替換為SUBSTI所表示的字符串;
行首/尾錨定:
${var/#PATTERN/SUBSTI}:
查找var所表示的字符串中,行首被PATTERN所匹配到的字符串,并將其替換為SUBSTI所表示的字符串;
${var/%PATTERN/SUBSTI}:
查找var所表示的字符串中,行尾被PATTERN所匹配到的字符串,并將其替換為SUBSTI所表示的字符串;
注意:PATTERN中使用glob風格的通配符;
例如:
]# userinfo="root:x:0:0:root admin:/root:/binb/chroot"
]# echo ${userinfo/root/ROOT}:顯示內容:ROOT:x:0:0:root admin:/root:/binb/chroot;替換第一次;
]# echo ${userinfo//r??t/ROOT}:顯示內容:ROOT:x:0:0:ROOT admin:/ROOT:/binb/chROOT;替換所有;
]# echo ${userinfo/#r??t/ROOT}:顯示內容:ROOT:x:0:0:root admin:/root:/binb/chroot;行首替換;
]# echo ${userinfo/%r??t/ROOT}:顯示內容:root:x:0:0:root admin:/root:/binb/chROOT;行尾替換;
查找刪除:
${var/PATTERN}:查找var字符串中,只刪除第一次被PATTERN匹配到的字符串;
${var//PATTERN}:查找var字符串中,刪除所有被PATTERN所匹配到的字符串;
${var/#PATTERN}:查找var所表示的字符串中,只刪除行首被PATTERN所匹配到的字符串;
${var/%PATTERN}:查找var所表示的字符串中,只刪除行尾被PATTERN所匹配到的字符串;
字符大小寫轉換:
${var^^}:把var中的所有小寫字符轉換成大寫字符;
${var,,}:把var中的所有大寫字符轉換成小寫字符;
例如:
]# url="http://www.magedu.com:80"
]# echo ${url^^}:顯示內容:HTTP://WWW.MAGEDU.COM:80;轉為大寫:
]# myurl=${url^^}
]# echo ${myurl,,}:顯示內容:http://www.magedu.com:80;轉為小寫;
變量賦值:
${var:-VALUE}:如果var變量為空,或未設置,則返回VALUE,否則,返回var變量值;
${var:=VALUE}:如果var變量為空,或未設置,則返回VALUE并將VALUE賦值給var變量,否則,返回var變量值;
例如:
變量hi為空,沒有定義值;
]# echo ${hi:-world}:顯示內容:world;
]# hi="hello"
]# echo ${hi:-world}:顯示內容:hello;
]# echo ${hi:=world}:顯示內容:hello;
]# unset hi:撤銷hi變量的值;
]# echo ${hi:=world}:顯示內容:world;
]# echo $hi:此時hi變量被賦值了;
${var:+VALUE}:如果var變量不空,則返回VALUE;否則,也沒什么意義;
${var:?ERRO_INFO}:如果var變量為空,或未設置,則返回ERRO_INFO為錯誤提示;否則,返回var的值;
練習:腳本完成如下功能;(一定完成,后續課程實驗要用到)
(1)提示用戶輸入一個可執行的命令的名稱;
(2)獲取此命令所依賴到的所有庫文件列表;
(3)復制命令至某目錄(例如/mnt/sysroot,即把此目錄當做根)下的對應的路徑中;
bahs,/bin/bash ==> /mnt/sysroot/bin/bash
usradd,/usr/sbin/useradd ==> /mnt/sysroot/usr/sbin/usradd
(4)復制此命令依賴到的所有庫文件至目錄目錄下的對應路徑下;
/lib64/ld-linux-x8664.so2 ==> /mnt/sysroot/lib64/ld-linux-x8664.so.2
進一步:
每次復制完成一個命令后,不要退出,而是提示用戶繼續輸入要復制的其它命令,并重復完成如上功能,直到用戶輸入“quit”退出腳本;
寫一個腳本:前面的練習解答
使用ping命令探測,172.16.1.1-172.16.67.1范圍內的所有主機是否在線,在線顯示為up,不在線顯示為down,分別統計主機數量;
分別使用for,while和until循環實現;
普通方式:
declare -i uphosts=0
declare -i downhosts=0
for i in {1..67};do
if ping -W 1 -c 1 172.16.$i.1 &>/dev/null;then
echo "172.16.$i.i is up"
let uphosts+=1
else
echo "172.16.$i.1 is down"
let downhosts+=1
fi
done
echo "Up hosts: $uphosts,Down hosts: $downhosts"
改版2:函數+while循環方式:
declare -i uphosts=0
declare -i downhosts=0
declare -i i=1
hostping() {
if ping -W 1 -c 1 $1 &>/dev/null;then
echo "$i is up"
let uphosts+=1
else
echo "$1 is down"
let downhosts+=1
fi
}
while [ $i -le 67 ];do
hostping 172.16.$i.1
let i++
done
echo "Up hosts: $uphosts,Down hosts: $downhosts"
改版3:使用return,在主程序中統計ping的主機數量;
declare -i uphosts=0
declare -i downhosts=0
declare -i i=1
hostping() {
if ping -W 1 -c 1 $1 &>/dev/null;then
echo "$i is up"
return 0
else
echo "$1 is down"
return 1
fi
}
while [ $i -le 67 ];do
hostping 172.16.$i.1
[ $? -eq 0 ] && let uphosts++ || let downhosts++
let i++
done
echo "Up hosts: $uphosts,Down hosts: $downhosts"
練習:腳本實現,探測C類、B類、A類網絡中的所有主機是否在線;
cping() {
local i=1
while [ $i -le 245 ];do
if ping -W 1 -c 1 $1.$i &>/dev/null;then
echo "$1.$i is up"
else
echo "$1.$i is down"
fi
let i++
done
}
#cping 192.168.0
bping() {
local j=0
while [ $j -le 255 ];do
cping $1.$j
let j++
done
}
#bping 172.16
aping() {
local x=0
while [ $x -le 255 ];do
bping $1.$x
let x++
done
}
aping 10
擴展:可改造以上腳本,提示用戶輸入任何網絡地址或網絡地址,獲取其網絡,并掃描其網段;
都可ping這個地址,先判斷ip地址類別,然后調用相應的函數,如輸入的是10.0.0.1,把ip地址第一段切出來進行比較是否為A(1-127)、B(128-191)、C(192-223))類網,然后再各自范圍內調用相應的函數實現;
bash腳本編程之信號捕捉
trap命令:就在腳本第一行,張網捕捉信號即可;
trap 'COMMAND' SIGANLS
-l:列出信號;
常可以進行捕捉信號:HUP、INT
COMMAND:可以使用分號分隔多個命令,還可以是一個函數;
注意:trap命令不能捕捉信號為:15-SIGTERM(terminal)和9-SIGKILL(kill);
因為捕捉信號的主要目的在于可以定義一旦信號到達以后,作出什么操作,可以不是默認操作;所以kill信號等不能隨意被捕捉;
查看信號列表:
]# trap -l
]# kill -l
查看信號類型解釋:
]# man 7 signal
例如:
trap 'echo "quit"; exit 1' INT
for i in {1..254};do
ping 172.16.$i.1
done
示例:使用trp捕捉,函數實現
ping172.16.1-254.1把結果保存在/tmp/目錄下創建的臨時目錄下,然后退出時,再刪掉那些臨時文件;
此腳本,不能刪除最后一次沒有捕捉到的臨時文件;
declare -a hosttmpfiles
trap 'mytrap' INT
mytrap() {
echo "quit"
rm -f ${hosttmpfiles[@]}
exit 1
}
for i in {1..50};do
tmpfile=$(mktemp /tmp/ping.XXXXX)
if ping -W 1 -c 1 172.16.$i.1 &>/dev/null;then
echo "172.16.$i.1 is UP" | tee $tmpfile
else
echo "172.16.$i.1 is down" | tee $tmpfile
fi
hosttmpfiles[${#hosttmpfiles[*]}]=$tmpfile
done
在bash中使用ACSII顏色:
格式:\033[前#景顏#色;背#景顏#色mSTRING\033[0m
\033[#;#;#mSTRING\033[0m
多種控制符可組合使用,彼此間用分號隔開;
\033[31mhello\033[0m
\033[:表示控制鍵Ctrl;
\033[0m:表示控制結束;
31m:表示前景色;
左側數字:(可同時設置前景、背景色)
3:表示前景色;
4:表示背景色;
右側數字:表示顏色;
1:紅色;
2:綠色;
3:金色;
4:藍色;
5:紫色;
6:青色;
7:灰色;
例如:\033[3mhello\033[0m
#m:表示字體
1:粗體;
4:加下劃線;
5:閃爍;
7:前背景反色;
8:隱藏;
例如:
]# echo -e "\033[31mhello\033[0m":前景色為紅色;
]# echo -e "\033[41mhello\033[0m":背景色為紅色;
]# echo -e "\033[41;32mhello\033[0m":前景為綠色,背景為紅色;
]# echo -e "\033[7mhello\033[0m":前背景反色;
]# echo -e "\033[4mhello\033[0m":加下劃線;
]# echo -e "\033[42;35;5mhello\033[0m":背景綠色,前景紫色,閃爍;
內置環境變量:PS1
命令行提示符格式;
可自定義命令行提示符格式:
export PS1='[\033[31m\u\033[0m@\033[32m\h\033[0m \033[35m\W\033[0m]\$'
但是設置后,有副作用;
例如:
]# echo $PS1
顯示內容:[\u@\h \W]\$
dialog命令:(需要yum install安裝)
display dialog boxes from shell scripts
dialog common-options box-options
box-options:
有三個參數:text、height、width
--calendar:日歷框;
--dselect:選擇目錄框;
--editbox:編輯框;
--form:表單;
--gauge:進度條;
--infobox:信息框;
--inputbox:輸入空;
--inputmenu:輸入菜單;
--menu:菜單;
--msgbox:消息框;
--passwordbox:密碼框,顯示為*號;
]# yum info dialog:查看dialog命令是否有可用安裝包;
]# yum install dialog:安裝dialog程序包;
例如:
]# dialog --msgbox hell 17 30:輸出消息框高17,寬30;
dialog命令可實現窗口化編程;有些是調用C庫中的curses實現的;
研究:
dialog命令,各窗體控件的使用方式;
如何獲取用戶選擇或鍵入的內容?dialog命令默認輸出信息被定向到錯誤輸出流;
a=$(dialog),結果應該會保存在變量中,但無法賦值給變量,需要在dialog命令應用選項--stdout;
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。