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,駝峯命名法 4.見名知義
在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 false 1 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/bin/nologin.sh和login.sh,實現禁止和充許普通用戶登錄系統。
[root@centos7 ~/bin#]cat nologin.sh #!/bin/bash # [ -e /etc/nologin ] && echo "已禁用普通用戶登錄系統" || touch /etc/nologin && echo "已禁用普通用戶登錄系統" [root@centos7 ~/bin#] [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
止戰