快速掌握Shell編程
作者原創作品,轉載請註明出處http://www.cnblogs.com/yangp/p/8511321.html。
(一)Shell編程概述
1.1 shell簡述
Shell編程和JavaScript非常相似,Shell和JavaScript都是弱類型語言,同時也都是解釋型語言。解釋型語言需要解釋器,JavaScript的解釋器是瀏覽器,Shell腳本的解釋器時bash,是一個shell、一個命令行用戶接口。
1.2 bash簡述
bash在執行或者解釋腳本的時候,此bash非彼bash。用戶登錄進來的時候就用一個bash。通過敲一個命令來解釋腳本的時候,是在當前bash中打開一個新的bash,這兩個bash是父子關系。其實在空行敲回車也是從當前bash打開新的bash。
在命令行中輸入$ps -aux |grep bash可以看見當前只有一個bash。這個bash就是系統和用戶進行交互的一個用戶接口。
在命令行中輸入命令bash回車。這就是打開了一個新的bash。再次輸入ps -aux |grep bash可以看見當前有兩個bash。因為敲一個命令就是一個程序的入口,這個命令就被啟動,這個程序就是bash。這兩個bash就是父子關系。這種有層次結構的bash可以使用exit退出。
(二)變量
2.1 變量的類型
shell腳本的變量實際上也是bash的變量,共有四種類型:環境變量、本地變量(或稱局部變量,但略有差別)、位置變量、特殊變量。環境變量,作用在當前bash和所有子bash,與本地變量的區別在於作用域不同;本地變量(局部變量,當前代碼段),作用在當前bash,所有子bash都不能用;位置變量和特殊變量,是bash內置的用來保存某些特殊數據的變量,也不存在作用域的問題(也叫系統變量)。
2.2 環境變量
環境變量,$export 變量名=值,其作用域為當前的shell和其子shell。
註意:腳本在執行時都會啟動一個子shell進程,命令行中啟動的腳本會繼承當前shell環境變量;系統自動啟動腳本(非命令行啟動),則需要自我定義環境變量。
2.3 本地變量(局部變量)
本地變量是只屬於某一個bash的變量。例如,$var_name=值,其作用域是整個bash進程。
局部變量類似,例如$local var_name =值,其作用域為當前代碼段。
2.4 位置變量
位置變量是指用於腳本執行的參數,$1表示第一個參數,以此類推$1,$2…。
2.5 特殊變量
特殊變量是每一個bash進程中特有的一些變量,無需聲明。總共就幾個,$?和$#j較為常用。
2.5.1 $?
$? 表示上一個命令的執行狀態返回值。事實上,任何一個程序執行的結果只有兩類,即程序有兩類返回值:
第一類是命令的執行結果。例如輸入$ls顯示的目錄下的所有目錄和文件就是執行結果,再如輸入$id -u root看見的0就是執行結果。
第二類是命令的執行狀態。任何命令的執行狀態都被$?這個變量存儲起來。$?: 0表示正確,1-255表示錯誤。例如使用$echo “$?”查看上一個命令的執行狀態,打印上一個命令的執行狀態0表示正確,非0表示錯誤。註意不能查看上上個命令的執行狀態,因為被覆蓋了。
2.5.2 $#
$# 表示傳遞到腳本的參數個數。
2.5.3 $*
$* 表示傳遞到腳本的參數,與位置變量不同,此選項參數可超過9個。
2.5.4 $$
$$ 表示腳本運行時當前進程的ID號,常用作臨時變量的後綴。
2.5.5 $!
$! 表示後臺運行的(&)最後一個進程的ID號 。
2.5.6 $@
$@ 與$#相同,使用時加引號,並在引號中返回參數個數。
2.5.7 $-
$- 表示上一個命令的最後一個參數 。
2.6 變量的聲明、撤銷、查看與引用
2.6.1 聲明變量
環境變量的聲明必須加export;本地變量和局部變量的聲明不需寫export,直接寫變量名後面接值即可。
2.6.2 撤銷變量
使用unset 變量名,撤銷變量。
2.6.3 查看變量
查看shell中變量:set
查看shell中的環境變量:printenv或env
2.6.4 引用變量
a) 引用變量
引用變量格式為:${變量名},一般可以省略{}。只有特殊情況下不能省略,具體效果見下圖:
b) 單引號:強引用
單引號:強引用,不作變量替換,引用字符串常量(單引號的內容都是字符串)。
c) 雙引號:弱引用
雙引號:弱引用,做變量替換。
d) 反引號:命令替換
反引號:``命令替換。當字符串表示一個命令,需要執行時,則要用反引號。
(三)輸出重定向
3.1 命令執行結果保存在一個文件中
3.1.1 >覆蓋重定向
$ls >/path/file。不會重定向錯誤結果
3.1.2 >> 追加重定向
$cat file1 file2 >> file3 //將file1和file2的文件內容追加重定向到file3後面。
3.1.3 2> 錯誤覆蓋重定向
程序執行出錯的結果放到文件中。
3.1.4 2>>錯誤追加重定向
程序執行出錯的結果放到文件中。
3.1.5 &> 全部覆蓋重定向
無論命令執行對錯,都會覆蓋重定向到文件。
3.1.6 &>> 全部追加重定向
無論命令執行對錯,都會追加重定向到文件。
3.2 命令執行結果直接丟棄
/dev/null文件,dev是設備,null是一個設備文件,稱之為數據黑洞,所有數據放到這裏都無法恢復。
$ls >> /dev/null
(四) 腳本
通過組織命令及變量來完成具有某種業務邏輯的功能稱之為腳本。
4.1 簡單腳本案例
4.1.1 案例一(添加用戶)
a) 業務描述
添加6個用戶,每個用戶的密碼同用戶名,不顯示添加密碼的信息,並給顯示添加用戶成功信息。
b) 編寫腳本
首先,創建腳本。
mkdir /opt/shell cd /opt/shell vim test1.sh
其次,編寫腳本主體。
#!/bin/bash#腳本的第一行一定是這個腳本的聲明,這裏聲明的是腳本的解釋器。 # 用戶可以有參數傳過來,也可以直接定義一個變量。 U=’user1’ #聲明變量,本地變量的聲明直接變量名=值,無需export。這裏選擇單引號,因為user1是個字 符串,只需強引就行,雙引號也行。 useradd $U #引用變量 #設置密碼和用戶名相同,但是設置完之後不顯示passwd的執行結果。但是這個passwd會在控制臺出現信息 ,所以需要使用管道傳入數據,並且重定向。 echo "$U" | passwd --stdin $U &>/dev/null #前一個命令的輸出傳給後一個命令的輸入。 echo "success." #打印成功信息。
以上是添加1個給定名稱的用戶,可以稍作修改,變為參數傳入的方式動態添加用戶:
cp test1.sh test2.sh vim test2.sh
#!/bin/bash # useradd $1 #這裏是使用傳遞參數的方式,1代表1個參數,實際上$1是特殊變量$*的特例 echo "$1" | passwd --stdin $1 &>/dev/null echo "Add user $1 success."
c) 執行腳本
這個兩個腳本當前沒有執行權限。有兩種方法使該腳本有執行權限:一是添加執行權限;二是,使用命令sh /path/腳本名(sh是一個執行腳本的命令)。
腳本1的執行:
$sh test1.sh
腳本2的執行:
$sh test2.sh username
查看是否添加成功:
$cat /etc/passwd
d) 查看腳本執行程度
$bash -x /opt/shell/test2.sh
4.1.2 案例二(刪除用戶)
a) 業務描述
寫一個腳本,完成以下任務:第一,使用一個變量保存一個用戶名;第二,刪除此變量中的用戶,且一並刪除其家目錄;第三,顯示“用戶刪除成功”信息。
b) 編寫腳本
首先,創建腳本。
vim /opt/shell/test3.sh
其次,編寫腳本主體。
#!/bin/bash U=$* #使用一個變量存儲用戶名 userdel $* #刪除這個用戶 rm -rf /home/$* #刪除該用戶的家目錄 echo "delete user and $*home success." #打印成功信息
c) 執行腳本
sh test3.sh username #註意一定要有參數(要刪除的用戶),不然會刪除整個/home目錄 cat /etc/passwd #查看用戶是否刪除成功。 ls /home #查看用戶的家目錄是否刪除成功
4.2 條件判斷
條件判斷是布爾類型,而shell是弱類型的語言,也即沒有類型,所以表達式只有真或假。
4.2.1 條件表達式
條件表達式有兩種形式:一是[ expression ],註意中括號和表達式之間一定要加空格隔開;二是,test expression,test+空格+表達式。
4.2.2 整數的比較
=:-eq 等於
!=:-ne 不等於
>:-gt 大於
<:-lt 小於
>=:-ge 大於或等於
<=:-le 小於或等於
4.2.3 邏輯運算
a) 命令的邏輯關系
在linux中,命令執行狀態:0為真,其他為假。
b) 邏輯與或非
可以使用離散數學的邏輯與或非來進行邏輯判斷。
邏輯與:&&。當第一個條件為假時,第二條件不用再判斷,最終結果已經有;當第一個條件為真時,第二條件必須得判斷。
邏輯或:||。
邏輯非:!。
c) 案例(命令邏輯關系的條件判斷)
業務描述:添加用戶前先判斷是否存在,如果存在就打印該用戶已存在,並且退出。
編寫腳本:
vim /opt/shell/test4.sh
#!/bin/bash # id $1 &>/dev/null && echo "User $1 exist" && exit 3 #使用命令的執行狀態作為邏輯判斷。id $1 如果用戶$1存在,則為0(真),需要繼續判斷後面的,所以執行echo,並且退出腳本,不再 創建該用戶。退出,3是非0,當前腳本執行不成功。
#上一行可以使用邏輯或的代替,見下兩行
#!id $1 &>/dev/null || echo "User $1 exist" #如果用戶存在id $1則為0,前面加了!就是非0,就為假。前面為假,對於邏輯或來說需要繼續判斷後面的,所以執行echo 打印用戶存在。
#id $1 &>/dev/null && exit 3 #用戶存在就退出 useradd $1 #添加用戶 id $1 &>/dev/null && echo "$1" | passwd --stdin $1 &>/dev/null #如果用戶創建成功了>,就執行後面的echo passwd添加密碼。 echo "Add user $1 success."
執行腳本:
sh test4.sh user1
4.2.4 if條件判斷
a) 語法結構
if條件表達式的語法結構為:
if [ 條件 ]; then 語句 elif [ 條件 ]; then 語句 else 語句 fi
b) if的邏輯與或
-a :並且
-o :或者
例如:
if [ $# -gt 1 -a $# -lt 3 -o $# -eq 2 ] ; then
如果,上一個命令傳入的參數個數大於1,並且小於3,或者等於2。&&是離散數學中的邏輯與,-a是一個邏輯運算符,並且的意思,不同於&&。
c) 案例(if語句)
業務描述:給定一個用戶,如果他的UID為0則顯示為管理員,否則顯示其他普通用戶。
編寫腳本:
vim /opt/shell/test5.sh
#!/bin/bash USER_ID=`id -u $1`&> /dev/null || exit 3 #註意這裏需要用反引號,這裏也不能將變量名取為UID,會和環境變量沖突,這樣聲明的變量是只讀的。 if [ $USER_ID -eq 0 ] ; then echo "admin." else echo "other." fi
執行腳本:
sh test5.sh root
4.3 算術運算符
4.3.1 算術運算符的類型
a) let 算術運算表達式
let C=$A + $B
b) $[算術表達式]
C = $[$A+$B]
c) $((算術表達式))
C=$(($A+$B))
d) expr 算術表達式
註意:表達式中各操作數及運算符之間要有空格。而且要使用命令引用。
C=`expr $A + $B`
4.3.2 案例(算術運算符的使用)
a) 業務描述
給定一個用戶,獲取其密碼警告期限,然後判斷用戶密碼使用期限是否已經小於警告期限,如果小於,則是顯示“WARN”,否則顯示密碼還有多少天到期。
密碼警告期限:距離警告的天數;密碼使用期限:密碼有效期減去使用的時間。
b) 編寫腳本
首先,需要知道:
$date +%s 可以獲得今天的秒數
$cat /etc/shadow 可以獲得密碼使用時間。
cat /etc/shadow | grep root
觀察上圖,這一行數據是以冒號隔開的。其中的17021是指,創建密碼的那天距離1970年1月1日的天數;0表示密碼有效期最短0天;99999表示密碼最多99999天有效;7表示有效期還剩7天時警告。
然後,編輯腳本如下:
vim /opt/shell/test6.sh
#!/bin/bash # UN=$1 #根據傳入的數據作為變量UN的值 C_D=`grep "^$UN" /etc/shadow | awk -F: ‘{print $3}‘ ` #grep "^$UN" /etc/shadow是從shadow中找到到以$UN這個用戶名開頭的,print $3是密碼的創建時間,這裏使用-F來指定:分隔符。加反引號是一個命令。 M_D=`grep "^$UN" /etc/shadow | awk -F: ‘{print $5}‘ ` #‘{print $5}‘是找到最長有效期 W_D=`grep "^$UN" /etc/shadow | awk -F: ‘{print $6}‘` #‘{print $6}‘是警告時間 NOW_D=$[`date +%s`/86400] #`date +%s`獲得當前系統時間(秒),使用數學運算符[]相當於數>學的括號,除以一天的秒數 U_D=$[$NOW_D-$C_D] #算出了使用的天數 L_D=$[$M_D-$U_D] #算出剩余的天數 if [ $L_D -le $W_D ];then #判斷註意空格,剩余時間小於等於警告時間輸出warn. echo "warn." else echo "left day is $L_D" #其他情況輸出剩余天數 fi
c) 執行腳本
sh test6.sh root
4.4 文件與字符串測試
4.4.1 文件與字符串測試語法
a) 文件測試
文件測試需要中括號 [ ]
-e FILE:測試文件是否存在
-f FILE:測試文件是否為普通文件
-d FILE:測試文件是否為目錄
-r 當前用戶有沒有讀的權限
-w 當前用戶有沒有寫的權限
-x 當前用戶有沒有執行的權限
b) 字符串測試
== 等號兩端需要空格
!=
-n string : 判斷字符串是否為空
-s string : 判斷字符串是否不空
4.4.2 案例(文件與字符串測試)
a) 業務描述
指定一個用戶名,判斷此用戶的用戶名和它的基本組,組名是否相同。
b) 編寫腳本
vim /opt/shell/test7.sh
#!/bin/bash # if !id $1 &>/dev/null ; then echo "No such user." exit 12 fi if [ $1 == `id -n -g $1` ] ;then #`id -n -g $1`是指輸入的用戶的組的名稱(-g是組id) echo "相同" else echo "不相同" fi
c) 執行腳本
sh test7.sh root
4.5 循環語句
4.5.1 for循環
a) for循環的語法結構
for 變量 in 列表; do 語句 done
b) for循環中的列表
for循環中的列表尤為重要,列表可以是以空格隔開的一組數,可以理解為數組;也可以動態生成列表。
靜態數組列表
以下是個靜態列表的實例:
for I in 1 2 3 4 5 ; do 語句 done
動態生成列表
動態生成列表有三種方式,分別為:
第一,{1..100}。中間兩個點,1-10可以成{1..10}
第二,seq [起始數] [跨度數] 結束數。seq相當於oracle中sequence,遞增的序列,[起始數]帶有中括號意為可以不給,默認為1;[跨度數]帶有中括號也可以不給,默認值為1;結束數必須給,不然會造成死循環。
第三,ls /PATH/ 文件列表。這個實際上是通過for循環遞歸目錄下的文件。
c) for循環案例一
業務描述:
將那些可以登錄的用戶查詢出來,並且將用戶的帳號信息提取出來,後放入/tmp/test.txt文件中,並在行首給定行號。
編寫腳本:
vim /opt/shell/test8.sh
#!/bin/bash # J=1 count=`wc -l /etc/passwd | cut -d‘ ‘ -f1` #wc -l /etc/passwd 可以獲得passwd文件的行數;cut -d‘ ‘ -f1中-d是指定切割符,-f1是取第一個。 #count=`wc -l /etc/passwd | awk -F " " ‘{print $1}‘` #也可以使用awk for I in `seq $count`;do #這裏使用seq生成列表,以1開始,跨度為1,到$count結束。 u_shell=`head -$I /etc/passwd | tail -1 | cut -d: -f7` #head取前I行,然後管道到tail -1拿到最後一行,實際上就是第I行,然後管道到cut按:切割,取第七個(shell)。 u_n=`head -$I /etc/passwd | tail -1 | cut -d: -f1` #取用戶 if [ $u_shell == ‘/bin/bash‘ ];then #判斷字符串是否相等,相等表示可以登錄。可以登錄>就將/tmp/users.txt echo "$J $u_n " >> /tmp/users.txt #輸出時加上編號,這裏不能使用I,因為I是行號,業務要求是新的序號,需要連續的。 J=$[$J+1] #相當於J++ fi done
執行腳本:
sh test8.sh root
d) for循環案例二
業務描述:
計算100以內所有能被3整除的整數的和。
編寫腳本:
vim test9.sh
#!/bin/bash # sum=0 for I in {1..100};do S=$[$I%3] #對3取模(求余數) if [ $S -eq 0 ];then #數字比較只能用-eq,不能使用等號 sum=$[$sum+$I] fi done echo "$sum"
執行腳本:
sh test9.sh
e) for循環案例三
業務描述:
傳給腳本一個參數:目錄,輸出該目錄中文件最大的,文件名和文件大小。
編寫腳本:
vim test10.sh
#!/bin/bash # #首先判斷參數是否正確 if [ -f $1 ];then #-f 判斷傳過來的目錄是否是文件 echo "Arg is error." exit 2 fi if [ -d $1 ];then #-d 是判斷$1是目錄 c=`du -a $1 | sort -nr | wc -l` #du -a $1是列出$1目錄下的所有文件和目錄; sort -nr是按數值降序排列; wc -l獲取行數。但是這樣把目錄頁選出來了 for I in `seq $c`;do f_size=`du -a $1 | sort -nr | head -$I | tail -1 | awk ‘{print $1}‘` #取到第I行,拿到文件大小 f_path=`du -a $1 | sort -nr | head -$I | tail -1 | awk ‘{print $2}‘` #取到第I行,拿到文件路徑 if [ -f $f_path ];then #判$f_path是否是文件,是文件就直接輸出,並退出循環 echo -e "the biggest file is $f_path \t $f_size" break fi done fi
執行腳本:
sh test10.sh /path/
4.5.2 while循環
a) 格式一
while 條件;do 語句 [break] done
b) 格式二(死循環)
while true do 語句 done
c) 格式三(死循環)
while : do 語句 done
d) 格式四(死循環)
while [ 1 ] do 語句 done
e) 格式五(死循環)
while [ 0 ] do 語句 done
f) while循環案例
業務描述:
使用echo輸出10個隨機數。
編寫腳本:
vim /opt/shell/test11.sh
#!/bin/bash # I=1 while [ $I -le 10 ] ; do echo -n "$RANDOM" I=$[$I+1] done
執行腳本:
sh test11.sh
作者原創作品,轉載請註明出處http://www.cnblogs.com/yangp/p/8511321.html。
快速掌握Shell編程