bash編程初體驗(一)
-
認識bash編程
-
變量與賦值
-
算術與邏輯運算
-
條件測試與退出狀態
認識bash編程
Bash(GNU Bourne-Again Shell)是許多Linux發行版的默認Shell,我們要認識的bash中,就是在bash的環境下的一種編程。
眾所周知,程序=指令+數據,由此也決定了兩種不同的編程風格,過程過與對象式;
過程式:以指令為中心,數據服務于指令 對象式:以數據為中心,指令服務于數據
而shell也給我們提供了一種編程能力,在shell上編寫的腳本程序,都是解釋執行,而不是通過編譯,因為我們Bash自身就是解釋器。
所有的編程語言都有三種基本的邏輯處理方式:順序結構、選擇結構、循環結構,傳說中,只要你掌握這三種結構,你就可以編織一個屬于自己的星球了,這就是《黑客帝國》的故事!
編程語言的基本結構:數據+表達式+語句,在Linux中,一切皆文件,而shell腳本是一個包含命令或聲明的文本文件,有一定的格式要求,# 表示注釋,且首行要符合shebang機制:
#!/bin/bash #!/usr/bin/python #!/usr/bin/perl
對于運維來說,shell腳本是一個解放我們生產力的工具,可自動化常用的命令;執行系統管理和故障排除;創建簡單的應用及處理文本與文件。
當寫完一個腳本之后,就需要運行,shell腳本的運行主要有兩種方法:
1.給予權限執行,在命令行上指定腳本的相對或絕對路徑 2.直接運行bash解釋器,將腳本作為解釋器程序的參數來運行
在以bash直接運行腳本時,有如下兩種調試方法:
1. bash -n /PATH/TO/SOME_SCRIPT 檢測腳本有無語法錯誤 2. bash -x /PATH/TO/SOME_SCRIPT 調試執行
一個簡單的shell腳本:
#!/bin/bash #author: liansir #Version: 1.0 #Description: display a Hello World! echo "Hello World!"
變量與賦值
變
量一詞,小學數學中應該就接觸到了,如常量,變量,常量就是指固定不變的量,在數學中就是一個給定的數值;變量就是指一個變化的量,形如x,
y之類的;而編程中的常量與變量與數學中的常量與變量其內含是極其相似的,只是它們存在的環境變了而已,在shell腳本編程中,變量就是一段命名的地址
空間,用變量可以為代表你想要表達的東西,譬如把變量看作“水果”的話,它可代表蘋果、桃子等。
在shell腳本中,變量有兩種類型:
強類型:定義變量時必須指定數據類型 弱類型:定義變量時無需指定數據類型
說到數據類型,就涉及到了數據的存儲格式,數據的存儲主要有字符型與數值型,而數值型主要有整形與浮點型,但是bash是不支持浮點型數據的。
再回到我們的變量,強類型的變量參與運算時一定得符合其類型要求;而弱類型的變量在參與運算時會自動進行隱式轉換。既然如此,變量的類型也就會有如下作用:
1.數據存儲格式 2.參與的運算3.表示的數據范圍
變量的命名法則:
1.不能程序中的保留字,如if, for 2.只能使用數字、字母及下劃線,且不能以數字開頭 3,駝峰命名法,見名知義
在bash中,根據變量的生效范圍主要有如下幾種類型的變量:
本地變量:生效范圍為當前shell進程,對當前shell的子shell及其它shell均無效 環境變量:生效范圍為當前shell進程及其子進程 局部變量:生效范圍為當前shell進程中的某代碼片斷(通常指函數) 位置變量:$1, $2, $3...來表示,用于讓腳本在代碼中調用通過命令行傳遞給它的參數 特殊變量:$?, $0, $*, $@, $# 只讀變量:只能聲明,不能修改與刪除, 可用readonly name或declare -r name來聲明。
在bash中,變量的賦值有直接賦值與引用賦值兩類,所謂直接賦值,類似于name=value,即直接給定一個變量名對其進行賦值,注意等號=兩邊無空格;引用賦值又分為變量賦值引用與命令賦值引用:
引用變量:name="$USER" 引用命令:name=`COMMAND` 或 name=$(COMMAND)
再來看看變量引用:$name, ${name}, 注意變量引用與引用變量的區別!
而引用也有強弱之別:
" ":弱引用,變量會被替換為變量值 ' ':強引用,變量會被當作原字符串
當我們要查看已經定義的所有變量時,可直接使用 set命令,而刪除變量則使用unset name.
環境變量,在bash中占有重要的一席之地,其聲明與賦值格式為:
export name=VALUE declare -x name=VALUE
通過以下命令來顯示環境變量:
export: 可聲明與顯示環境變量 env:顯示系統中已存在的環境變量 printenv:類似于env
小結下bash中的環境變量:
USER, UID, PATH, SHELL, HOME, HISTSIZE, HISTFILE, HISTFILESIZE, HISTCONTROL, HISTTIMEFORMAT, PS1, PWD, OLDPWD
另外,位置變量也不容我們小覷??!
$0: 命令本身 $1,$2:對應第1與第2個參數,shift [n]變換位置 $*: 傳遞給腳本的所有參數,且全部參數合為一個字符串 $@: 傳遞給腳本的所有參數,每個參數為獨立字符串 $#: 傳遞給腳本的參數個數 注:$@ 與 $* 只在被雙引號引起來的時候有差異
練習
1、編寫腳本/root/bin/systeminfo.sh,顯示當前主機系統信息,包括主機名,IPv4地址,操作系統版本,內核版本,CPU型號,內存大小,硬盤大小。
[root@centos7 ~/bin#]cat systeminfo.sh #!/bin/bash # echo -e "The following is systeminfo:\n" echo "The hostname is `hostname`" echo "The IP is `ifconfig |sed -n '2p' |sed -n -r 's@.*inet.(.*) net.*@\1@p'`" echo "The OS version is `cat /etc/redhat-release`" echo "The kernel version is `uname -r`" echo "The CPU is `cat /proc/cpuinfo |grep 'model name' |sed -n '1p' |cut -d: -f2 |tr -d ' 'i`" echo "The memory size is `cat /proc/meminfo |sed -n '1p' |cut -d: -f2 |tr -d ' '`" echo "The disk size is `fdisk -l |sed -n '2p' |cut -d: -f2 |cut -d, -f1`" [root@centos7 ~/bin#]
相對路徑執行(要給予其執行權限)
2、編寫腳本/root/bin/backup.sh,可實現每日將/etc/目錄備份到/root/etcYYYY-mm-dd中.
[root@centos7 ~/bin#]cat baskup.sh #!/bin/bash # cp -r /etc/ /root/etc`date +%F` [root@centos7 ~/bin#]
絕對路徑執行(要給予執行權限)
3、編寫腳本/root/bin/disk.sh,顯示當前硬盤分區中空間利用率最大的值
[root@centos7 ~/bin#]cat checkdisk.sh #!/bin/bash # NumDisk=`df |grep 'sd' |tr -s ' ' |cut -d' ' -f5 |cut -d% -f1` [[ $NumDisk -gt 50 ]] && wall disk will be full!;exit [root@centos7 ~/bin#]
直接利用bash執行(不需要執行權限)
4、編寫腳本/root/bin/links.sh,顯示正連接本主機的每個遠程主機的IPv4地址和連接數,并按連接數從大到小排序.
[root@centos7 ~/bin#]cat links.sh #!/bin/bash # echo -e "遠程主機的IP地址及連接數:\n"netstat -nt | tr -s ' ' |cut -d' ' -f5 |tail -n +3 |cut -d: -f1 |sort |uniq -c [root@centos7 ~/bin#]
算術邏輯運算
算術運算
bash中的運算:命令let
help let:
id++, id-- variable post-increment, post-decrement ++id, --id variable pre-increment, pre-decrement -, + unary minus, plus !, ~ logical and bitwise negation ** exponentiation *, /, % multiplication, division, remainder +, - addition, subtraction <<, >> left and right bitwise shifts <=, >=, <, > comparison ==, != equality, inequality & bitwise AND ^ bitwise XOR | bitwise OR && logical AND || logical OR expr ? expr : expr conditional operator =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |= assignment
實現算術運算的幾種方式:
1.let var=算術表達式 2.var=$[算術表達式] 3.var=$((算術表達式)) 4.var=$(expr arg1 arg2 arg3...) 5.declare -i var = 數值6.echo '算術表達式' |bc 注:乘法 * 在有些場合中需要轉義 \* bash內有內建的隨機數生成器:$RANDOM (1-32767) echo $[$RANDOM%50]: 0-49之間的隨機數
除此,還有以下增強型賦值:
+=, -=, *=, %= /= let var+=3 自加3后自賦值 let var++ 自加1 let var-=1 自減1后自賦值 let var-- 自減1
邏輯運算
邏輯運算,又稱布爾運算,就是關于一個事件真或假的運算,通常用來測試真假值。
true false1 0與:同真為真,一假為假 或:同假為假,一真為真 非:非真為假,非假為真 短路運算: 短路與: 第一個為0,結果必定為0 第一個為1,第二個必須要參與運算 短路或: 第一個為1,結果必定為1 第一個為0,結果必須要參與運算 異或:^ 不同為真,相同為假
練習
1:寫一個腳本/root/bin/sumid.sh,計算/etc/passwd文件中的第10個用戶和第20用戶的ID之和。
[root@centos7 ~/bin#]cat sumid.sh #!/bin/bash # num1=`cat /etc/passwd |sed -n -e '10p' |cut -d: -f3` num2=`cat /etc/passwd |sed -n -e '20p' |cut -d: -f3` let var=$num1+$num2echo "第十個用戶與第二十個用戶的UID之和為:"echo $var [root@centos7 ~/bin#]
2:寫一個腳本/root/bin/sumspace.sh,傳遞兩個文件路徑作為參數給腳本,計算這兩個文件中所有空白行之和.
[root@centos7 ~/bin#]cat sumspace.sh #!/bin/bash # fSpace1=`grep '^$' $1 |wc -l` fSpace2=`grep '^$' $2 |wc -l` sumSpace=$[ $fSpace1+$fSpace2 ]echo "兩個文件的空白行之和為:$sumSpace" [root@centos7 ~/bin#]
3:寫一個腳本/root/bin/sumfile.sh,統計/etc, /var, /usr目錄中共有多少個一級子目錄和文件.
[root@centos7 ~/bin#]cat sumfile.sh #!/bin/bash # #統計/etc, /var, /usr目錄中共有多少個一級子目錄和文件 Detc=`tree -L 1 /etc/ |wc -l` Fetc=`ls -lR /etc/ |wc -l` Dvar=`tree -L 1 /var/ |wc -l` Fvar=`ls -lR /var/ |wc -l` Dusr=`tree -L 1 /usr/ |wc -l` Fusr=`ls -lR /usr/ |wc -l` echo "/etc目錄中共有一級子目錄$Detc 個,文件 $Fetc 個" echo "/var目錄中共有一級子目錄$Dvar 個,文件 $Fvar 個" echo "/usr目錄中共有一級子目錄$Dusr 個,文件 $Fusr 個" [root@centos7 ~/bin#]
條件測試與退出狀態
退出狀態
每
一條命令的執行,要么成功,要么失敗,要么成功一半,失敗一半,(這種情況嚴格地說屬于失敗),對于我們的shell腳本而言,也是一樣的,shell腳
本不就是把簡單的命令通過有效的組織,以編程的思想而實現的么!這也不就是Linux的哲學之一,將單一用途的命令通過組合而完成復雜的任務!
對于我們用戶而言,一個腳本執行成功與否,一般情況下通過肉眼凡胎也能看見,大不了 echo $? 一下,返回值為0,則表示執行成功,為1-255之間的數則表示錯誤。至此,腦袋稍微一轉便知Linux系統就是以返回值為0或為1而判斷命令是否執行成功的。一個程序的發起就是一個進程,程序的運行有期壽命,進程自然也有其退出的狀態,而進行就是通過退出狀態來報告成功與失敗的。
0 代表成功,1-255代表失敗$? 變量保存最近命令的退出狀態 另外,有兩種聚焦命令的方法: 復合式:date; who |wc -l 命令會一個接一個地運行 子shell: (date; who |wc -l) >> /tmp/trace 所有的輸出都被發送給單個STDOUT和STDERR
既然 echo $? 可以查找最近一個命令的退出狀態碼,靈活的LInux也允許我們自定義退出狀態碼:exit [n]
注意:腳本中一旦遇到exit命令,腳本會立即終止,終止退出狀態取決于exit命令 后面的數字;如果未給腳本指定的退出狀態碼,整個腳本狀態執行碼取決于腳本中 執行的最后一條命令的狀態碼。
條件測試
所謂條件測試,就是判斷某需求是否滿足,需要專門的測試機制來實現,測試機制必然有相應的表達式與測試命令。
測試命令:
test EXPRESSION [ EXPRESSION ][[ EXPRESSION ]]注:EXPRESSION前后必須要有空白字符
test
test - check file types and compare values ( EXPRESSION ) EXPRESSION is true ! EXPRESSION EXPRESSION is false 組合條件測試: COMMAND1 && COMMAND2 COMMAND1 || COMMAND2 ! COMMAND 如:[ -e FILE ] && [-r FILE ] EXPRESSION1 -a EXPRESSION2 both EXPRESSION1 and EXPRESSION2 are true EXPRESSION1 -o EXPRESSION2 either EXPRESSION1 or EXPRESSION2 is true ! EXPRESSION 必須使用測試命令進行: ~#] [ -z "$HOSTNAME" -o "$HOSTNAME"=="localhost.localdomain" ] && hosname liansir.com ~#] [ -f /etc/cat -a -x /etc/cat ] cat /etc/issue 注:&& 與 || 可視為“條件性”操作符,靈活運行這兩個操作符,可達到簡單if語句 的作用; && 代表條件性的 AND THEN || 代表條件性的 OR ELSE ------------------------------------------------------------------------------ -n STRING the length of STRING is nonzero -z STRING the length of STRING is zero ==: 是否等于 >: 是否大于,比較的是ascii碼 <: 是否小于 =~ 左側字符串是否能夠被右側的PATTERN所匹配 注:此表達式一般用于[[ ]] 中; 用于字符串比較時用到的操作數都應該使用引號; ------------------------------------------------------------------------ 數值測試: INTEGER1 -eq INTEGER2 INTEGER1 is equal to INTEGER2 INTEGER1 -ge INTEGER2 INTEGER1 is greater than or equal to INTEGER2 INTEGER1 -gt INTEGER2 INTEGER1 is greater than INTEGER2 INTEGER1 -le INTEGER2 INTEGER1 is less than or equal to INTEGER2 INTEGER1 -lt INTEGER2 INTEGER1 is less than INTEGER2 INTEGER1 -ne INTEGER2 INTEGER1 is not equal to INTEGER2 ------------------------------------------------------------------------ 文件測試: 雙目測試: FILE1 -ef FILE2 FILE1 and FILE2 have the same device and inode numbers # FILE1與FILE2是否指向同一設備上的相同inode號 FILE1 -nt FILE2 FILE1 is newer (modification date) than FILE2 # FILE1是否新于FILE2 FILE1 -ot FILE2 FILE1 is older than FILE2 # FILE1是否舊于FILE2 存在性及類別測試 -b FILE FILE exists and is block special -c FILE FILE exists and is character special -d FILE FILE exists and is a directory -e FILE FILE exists -f FILE FILE exists and is a regular file -h FILE FILE exists and is a symbolic link (same as -L) -L FILE FILE exists and is a symbolic link (same as -h) -p FILE FILE exists and is a named pipe -S FILE FILE exists and is a socket 文件權限測試 -r FILE FILE exists and read permission is granted -w FILE FILE exists and write permission is granted -x FILE FILE exists and execute (or search) permission is granted 文件特殊權限測試 -u FILE FILE exists and its set-user-ID bit is set -g FILE FILE exists and is set-group-ID -k FILE FILE exists and has its sticky bit set 文件大小測試 -s FILE FILE exists and has a size greater than zero 是否存在且非空 文件是否打開: -t FD file descriptor FD is opened on a terminal 文件描述符是否已經被打開且與某終端相關 -O FILE FILE exists and is owned by the effective user ID 當前有效用戶是否為文件屬主 -G FILE FILE exists and is owned by the effective group ID 當前有效用戶是否為文件屬組 -N FILE 文件自上一次被讀取之后是否被修改過
對于test命令,有以下兩種格式:
長格式: test "$A" == "$B" && echo "String are equal" test "$A" -eq "$B" && echo "String are equal" 簡寫: [ "$A" == "$B" ] && echo "String are equal" [ "$A" -eq "$B" ] && echo "String are equal"
練習
-
寫一個腳本/root/bin/argsnum.sh,接受一個文件路徑作為參數;如果參數個數小于1,則提示用戶“至少應該給一個參數”,并立即退出;如果參數個數不小于1,則顯示第一個參數所指向的文件中的空白行數。
[root@centos7 ~/bin#]cat argsnum.sh #!/bin/bash # [[ $# -lt 1 ]] && echo "至少應該給一個參數" [[ $# -ge 1 ]] && grep '^$' $1 |wc -l [root@centos7 ~/bin#]
-
寫一個腳本/root/bin/hostping.sh,接受一個主機的IPv4地址做為參數,測試是否可連通。如果能ping通,則提示用戶“該IP地址可訪問”;如果不可ping通,則提示用戶“該IP地址不可訪問”.
[root@centos7 ~/bin#]cat hostping.sh #!/bin/bash # [[ $# -eq 1 ]] && (echo "$1" |grep -q -E '\<((([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\>' && ( ping -c1 -W1 $1 &> /dev/null && echo "該IP地址可訪問!" || echo \ "該IP地址不可訪問!" )) || echo "請給出一個合法IP地址!"; exit 2[root@centos7 ~/bin#]
-
chmod -rw /tmp/file1,編寫腳本/root/bin/per.sh,判斷當前用戶對/tmp/fiile1文件是否不可讀且不可寫。
[root@centos7 ~/bin#]cat per.sh #!/bin/bash #[ ! -r $1 ] && [ ! -w $1 ] && echo "當前用戶對$1 文件不可讀且不可寫!" [root@centos7 ~/bin#]
[root@centos7 ~/bin#]cat nologin.sh #!/bin/bash # [ -e /etc/nologin ] && echo "已禁用普通用戶登錄系統" || touch /etc/nologin && echo "已禁用普通用戶登錄系統" [root@centos7 ~/bin#]
-
編寫腳本/root/bin/nologin.sh和login.sh,實現禁止和充許普通用戶登錄系統。
[root@centos7 ~/bin#]cat login.sh #!/bin/bash # [ -e /etc/nologin ] && rm -f /etc/nologin && echo "已允許普通用戶登錄系統!" [root@centos7 ~/bin#]cat nologin.sh #!/bin/bash # [ -e /etc/nologin ] && echo "已禁用普通用戶登錄系統" || touch /etc/nologin && echo "已禁用普通用戶登錄系統" [root@centos7 ~/bin#]
先禁用liansir用戶:
允許liansir用戶登錄:
-
計算1+2+3+…+100的值
[root@centos7 ~/bin#]cat 100sum.sh #!/bin/bash # # 計算1-100或100-1之間的和 echo "1-100之和為:"echo {1..100} |tr ' ' '+' |bc [root@centos7 ~/bin#]
-
計算從腳本第一參數A開始,到第二個參數B的所有數字的總和,判斷B是否大于A,否提示錯誤并退出,是則計算之。
[root@centos7 ~/bin#]cat sumAB.sh #!/bin/bash # [[ $# -eq 2 ]] && ( [[ $1 -lt $2 ]] && seq $1 $2 |tr '\n' '+' |grep -o '.*[^+]' |bc \ || echo "$1 大于 $2,無法計算!") || echo "請給出兩個數值!" [root@centos7 ~/bin#]
小探位置變量:
位置變量:用于讓腳本在代碼中調用通過命令行傳遞給它的參數
-
$1,$2.. $10,$11…MAX
-
$*,$@ 的區別
-
$#
位置變量知多少?
既然參數的個數與所有參數的顯示都正常,那為什么從第十個參數起位置變量就引用出錯呢?
再看:
至此,我們需要對比一下weizhi1.sh與weizhi2.sh的腳本了。
于是,我們可得出以下結論:$1-$9代表第一到第九個參數,但第十及以上的參數需要用大括號表示,如${10}。
$* 與 $@ 都表示所有參數,那二者又有何區別?
[root@centos7 ~/bin#]cat wzhi.sh #!/bin/bash #e cho "命令本身: $0" echo "1st is $1" echo "2st is $2" echo "all agrs are $*" echo "all agrs are $@" [root@centos7 ~/bin#]
可見$0表示命令本身,這個并不難理解。但$@ 與$*的區別還是看不出來!
[root@centos7 ~/bin#]cat wzhi2.s #!/bin/bash#wzhi.sh "$*" echo ------------------------------ wzhi.sh "$@" [root@centos7 ~/bin#]
由此可見,$*中在參數會被看成是一個整體,$@中的每個參數是獨立的,注意這是在被雙引號引起的時候。
如果把雙引號換作單引號:
最終,我們有了這樣的結論:表示位置變量的$* 與 $@ 在被雙引號引起的時候是有差別的,$*中在參數會被看成是一個整體,$@中的每個參數是獨立的;而如果是被單引號引起來,則二者并無任何差別。
bash編程初體驗(一)先到這兒!
2016.8.13
止戰
原創文章,作者:Liansir,如若轉載,請注明出處:http://www.www58058.com/34776
文章思路清晰,很有邏輯感,內容充實,排版精美,