1. 程式人生 > >linux入門系列8--shell程式設計入門

linux入門系列8--shell程式設計入門

本文將結合前面介紹的Linux命令、管道符等知識,通過VI編輯器編寫Shell指令碼,實現能自動化工作的指令碼檔案。

在講解Linux常用命令“linux入門系列5--新手必會的linux命令”一文中已提到,Shell終端直譯器是人機互動的橋樑,是使用者與核心之間的翻譯官。它作為使用者與Linux系統內部通訊的媒介,為使用者提供了一個面向Linux核心傳送請求以便執行處橫向的介面系統級程式,使用者可以通過Shell啟動、掛起、停止程式。

一、Shell指令碼及程式設計概述

1.1 Shell不僅是終端直譯器

實際上Shell不僅是一個命令列直譯器,除了能通過它控制程式的啟動、停止、掛起等操作外,它還是一個強大的程式語言,易編寫、易除錯、靈活性強。Shell是解釋執行的指令碼語言,可以呼叫Linux系統命令

Shell除了能夠支援各種變數和引數外,還提供了諸如迴圈、分支等高階程式語言才有的控制特性。要想正確使用這些特性,準確下達命令尤為重要,在講解具體程式設計語法之前,先學習一下Shell指令碼的執行方式。

1.2 Shell指令碼工作方式

Shell指令碼命令的工作方式有兩種:互動式和批處理

  • 互動式:Interactive,使用者沒輸入一條命令就立即執行。到目前為止前文采取的都是這種方式。
  • 批處理:Bach,事先編寫一個完整的Shell指令碼,一次性執行指令碼中的諸多指令。接下來我們將採用的就是此種方式。

通過SHELL環境變數,可以檢視當前系統預設使用的終端直譯器,預設採用Bash直譯器

[root@heimatengyun ~]# echo $SHELL
/bin/bash

1.3 第一個Shell指令碼

在Shell指令碼中不僅會用到前文學習過的Linux命令、管道符、資料流重定向等語法規則,還需要把內部功能模組化後通過邏輯語句進行處理,最終形成日常所見的shell指令碼。

約定俗成的所有程式語言第一個程式都是Hello World,我們用Shell編寫一個簡單的指令碼

[root@heimatengyun test]# ls
test1.txt  test2.txt
[root@heimatengyun test]# echo "hello world"
hello world
[root@heimatengyun test]# vi hello.sh
#!/bin/bash
#author:heimatengyun
echo " hello world"

儲存並退出。Shell指令碼檔案可以是任意名稱,但是為了避免被誤認為是普通檔案,建議字尾採用.sh,以表示是一個指令碼檔案。

ps:第一行#!/bin/bash是指令碼宣告,用來告訴系統使用哪種Shell直譯器來執行指令碼。

第二行#author:heimatengyun是註釋資訊,對指令碼功能進行介紹方便日後維護。

第三行echo " hello world"是命令語句,可以是簡單的各種linux命令,也可以是通過各種邏輯語句和語法規則組合的複雜語句塊。

Shell程式設計格式就這麼簡單,我稱之為Shell程式設計“三段式”,以後編碼就按照這個格式編寫即可。

1.4 執行Shell指令碼

Linux中執行指令碼有兩種方式:用直譯器直接執行、通過指令碼路徑執行

  • 用bash直譯器直接執行

語法:bash或sh 指令碼名稱

案例:執行前文建立的hello.sh指令碼檔案

[root@heimatengyun test]# bash hello.sh 
 hello world
[root@heimatengyun test]# sh hello.sh 
 hello world
  • 通過指令碼路徑執行

語法:指令碼的絕對路徑或相對路徑

案例:執行前文建立的hello.sh指令碼檔案

[root@heimatengyun test]# /root/test/hello.sh
-bash: /root/test/hello.sh: Permission denied
[root@heimatengyun test]# ./hello.sh
-bash: ./hello.sh: Permission denied

納尼?提示許可權不足。是的,你沒看錯,如果通過路徑這種方式執行,需要修改檔案的執行許可權,(預設的許可權不可以執行,並且在資料指令碼路徑時按tab鍵也無補全提示)檔案許可權相關命令將在後續文章中繼續講解,此處先按照如下命令新增指令碼的可執行許可權即可。

[root@heimatengyun test]# ll
total 12
-rw-r--r--. 1 root root  4 Dec  1 09:48 hello.sh
[root@heimatengyun test]# chmod 777 hello.sh 
[root@heimatengyun test]# ll
total 8
-rwxrwxrwx. 1 root root 53 Dec  1 09:22 hello.sh

再次執行,此時輸入路徑在按tab鍵也會提示自動補全了

[root@heimatengyun test]# ./hello.sh 
 hello world
[root@heimatengyun test]# /root/test/hello.sh 
 hello world

二、Shell程式設計語法

掌握Shell指令碼執行方式後,我們正式開始進入Shell程式設計語法學習。

任何一門語言的學習都沒有速成方法,都離不開大量的練習,只有多敲才能更加熟練,才能理解的更加深刻。

2.1 變數

Linux Shell(此處為預設bash)中的變數分為系統變數和使用者自定義變數,系統變數包括$HOME、$PWD、$SHELL、$USER等,使用者自定義變數則為使用者根據實際需要自定義的變數。

可以通過set命令檢視Shell中所有變數。

[root@heimatengyun test]# set
BASH=/bin/bash
...省略部分內容

下文主要演示自定義變數

2.1.1 定義變數

語法:

變數名=值

變數命名規則:

​ (1)變數名可以由字母、數字和下劃線註冊,但不能以數字開頭

​ (2)等號兩側不能有空格

​ (3)變數名一般大寫

案例:

(1)定義變數並使用

[root@heimatengyun test]# SRT="wellcome"
[root@heimatengyun test]# echo $SRT
wellcome
[root@heimatengyun test]# set |grep SRT
SRT=wellcome
[root@heimatengyun test]# env |grep SRT
[root@heimatengyun test]# 

可以看到自定義變數SRT,可以通過set命令查詢出來。

(2)變數提升為全域性環境變數

[root@heimatengyun test]# export SRT
[root@heimatengyun test]# env |grep SRT
SRT=wellcome

上例中自定義的變數SRT通過env命令檢視,並未在環境變數中查詢出來,通過export將自定義變數SRT提升為環境變數後,就可以通過env查詢出來

(3)撤銷變數

[root@heimatengyun test]# unset SRT
[root@heimatengyun test]# echo $SRT

[root@heimatengyun test]# set |grep SRT
[root@heimatengyun test]# env |grep SRT

撤銷變數使用unset命令,撤銷之後變數將不存在

2.2 變數賦值

除了直接賦值,還可以將命令執行的結果賦值給變數

語法:

變數=`命令`變數=$(命令)

說明:

​ 命令用反引號或$()包含起來,先執行命令然後將命令執行結果賦值給變數。

案例:

[root@heimatengyun test]# ls
hello.sh  test1.txt  test2.txt
[root@heimatengyun test]# RESULT=`ls`
[root@heimatengyun test]# echo $RESULT
hello.sh test1.txt test2.txt
2.3 位置引數變數

位置引數變數主要用於取指令碼的引數值,語法如下

變數名稱 功能
$n n為數字,$0表示命令本身,$1-9表示第一到第九個引數,十以上的引數需要用大括號包含,如第十個引數為${10}
$* 表示命令列中所有的引數,把引數看成一個整體
$@ 表示命令列中所有的引數,把每個引數區分對待
$# 表示命令列中所有引數的個數

案例:

輸入2個引數,計算倆個數的和並列印輸出

[root@heimatengyun test]# vi sum.sh
#sum
#!/bin/bash
#分別接收2個引數
num1=$1
num2=$2
#求和
sum=$(($num1+$num2))
#列印
echo $sum

儲存並退出,執行指令碼輸入2個數檢視執行結果

[root@heimatengyun test]# bash sum.sh 1 2
3
2.4 預定義變數

預定義變數都有特殊的作用,參看下表

變數名 功能
$? 表示當前程序中最後一條命令直接的返回狀態。0:執行成功;非0:執行失敗
$$ 當前程序號(PID)
$! 後臺執行的最後一個程序的程序號(PID)

案例:

(1)檢視當前程序和後臺執行的最後一個程序

[root@heimatengyun test]# vi mypid.sh 
#!/bin/bash
#輸出當前程序PID,也就是當前指令碼執行時生成的PID
echo "當前程序PID=$$"
echo "最後一個後臺程序PID=$!"

儲存退出,執行指令碼

[root@heimatengyun test]# bash mypid.sh 
當前程序PID=7810
最後一個後臺程序PID=

可以看到$$和 $!的區別,$$表示當前程序PID,而$!表示是後臺最有執行的一個程序的PID。

(2)輸出當前程序PID,並檢視上一次命令執行結果

[root@heimatengyun test]# vi pid.sh
#!/bin/bash
#輸出當前程序PID,也就是當前指令碼執行時生成的PID
echo "當前程序PID=$$"
#通過ls命令,查詢不存在的檔案,&表示讓命令後臺執行
ls -l XXX.txt&
echo "最後一個程序PID=$!"
echo "最後一條命令執行結果:$?"

儲存退出,執行指令碼

[root@heimatengyun test]# bash pid.sh 
當前程序PID=7395
最後一個程序PID=7396
最後一條命令執行結果:0
[root@heimatengyun test]# ls: cannot access XXX.txt: No such file or directory

[root@heimatengyun test]# 

可以看到命令執行的程序和當前指令碼執行的程序不是同一個,並且雖然xxx.txt檔案不存在,但結果仍然返回為0。如果改為查詢一個已經存在的檔案,毋庸置疑,返回結果肯定仍然為0。也就是說上邊指令碼不管命令執行是否成功,都將返回0,原因是通過&讓命令後臺執行,實際上是新開了一個程序,而$?只能獲取到當前程序的最後一次執行命令結果,因此在做判斷命令是否執行成功是特別要小心。

2.2 運算子和表示式

語法:

(1)$((運算式)) 或 $[運算式]

(2)expr m + n 注意expr運算子間要有空格

案例:

(1)採用$(())實現兩數相加

[root@heimatengyun test]# S=$((2+3))
[root@heimatengyun test]# echo $S
5

(2)採用$[]實現兩數相加

[root@heimatengyun test]# SUM=$[2+3]
[root@heimatengyun test]# echo $SUM 
5

(3)採用expr命令實現兩數相加

[root@heimatengyun test]# S1=`expr 2 + 3` 
[root@heimatengyun test]# echo $S1
5

注意expr命令時,操作符之間一定要有空格,否則執行的不是計算,而是字串連線,如下示例演示了有空格和無空格的區別,另外乘法符號*由於與萬用字元衝突,因此需要用\轉義

[root@heimatengyun test]# expr 2 + 3
5
[root@heimatengyun test]# expr 2+3
2+3
[root@heimatengyun test]# expr 2\*3
2*3
[root@heimatengyun test]# expr 2 \* 3
6
[root@heimatengyun test]# expr 2 * 3 
expr: syntax error

(4)採用expr命令計算“2加3的和乘以5”

[root@heimatengyun test]# S2=`expr 2 + 3`
[root@heimatengyun test]# echo $S2
5
[root@heimatengyun test]# expr $S2 \* 5
25

注意操作符之間的空格,以上為分步計算,也可以直接一步計算,多層巢狀是注意需要轉義反引號,如下:

[root@heimatengyun test]# expr `expr 2 + 3` \* 5
25
[root@heimatengyun test]# echo `expr \`expr 2 + 3\` \* 5`
25

2.3 條件判斷語句

2.3.1 條件判斷基本使用

語法:[ 條件 ]

說明:條件前後必須要有空格,非空返回true,可以使用$?驗證(0:true,非0:false)

案例:

(1)分別判斷存在和不存在的變數,檢驗返回值

[root@heimatengyun test]# [ $HOME ]  
[root@heimatengyun test]# echo $?    
0
[root@heimatengyun test]# [ $TEST ]      
[root@heimatengyun test]# echo $?    
1

由於$HOME是環境變數肯定存在,因此返回0,表示條件滿足,變數不為空;而TEST變數由於沒定義,所以不存在,返回非0。

(2)條件判斷語句用於邏輯判斷

[root@heimatengyun test]# [ $HOME ]&& echo ok || echo notok
ok
[root@heimatengyun test]# [ $TEST ]&& echo ok || echo notok     
notok

類似於其他語言中的三元運算子,條件滿足則執行緊隨其後的語句,否則不執行。

2.3.2 常用判斷條件
  • 兩個整數之間比較
符號 含義
= 字串比較
-lt 小於
-le 小於等於
-eq 等於
-gt 大於
-ge 大於等於
-ne 不等於
  • 按檔案許可權判斷
符號 含義
-r 有讀的許可權
-w 有寫的許可權
-x 有執行的許可權
  • 按檔案型別判斷
符號 含義
-f 檔案存在且是一個常規檔案
-d 檔案存在並且是一個目錄
-e 檔案存在
  • 案例

(1)整數比較

[root@heimatengyun test]# [ 1 -gt 2 ]
[root@heimatengyun test]# echo $?    
1

1不大於2,所以輸出1,表示false

(2)檔案型別判斷

[root@heimatengyun test]# [ -f test1.txt ]
[root@heimatengyun test]# echo $?
0
[root@heimatengyun test]# [ -f xxxx.txt ]     
[root@heimatengyun test]# echo $?        
1

test1.txt檔案存在,輸出0表示true;而xxxx.txt檔案不存在,所以輸出1表示false

(3)檔案許可權判斷

[root@heimatengyun test]# ll
-rw-r--r--. 1 root root   9 Nov 30 20:43 test1.txt
[root@heimatengyun test]# [ -x test1.txt ]
[root@heimatengyun test]# echo $?
1

由於test1.txt 無執行許可權,因此返回1,表示false

2.4 流程控制語句

流程控制結構分為:順序結構、分支結構、迴圈結構。順序結構根據語句依次順序執行,分支結構根據條件判斷執行不同的分支,迴圈結構則根據條件判斷是否迴圈執行。

2.4.1 分支語句

分支語句分為:if判斷語句、case語句

  • if判斷

語法:


if [ 判斷條件 ]

then

​ 程式

fi


或者then寫到判斷條件之後,用逗號隔開,等效於上邊語句

if [ 判斷條件 ];then

​ 程式

fi


if判斷還可以多層巢狀,形如:

if [ 判斷條件1 ]

then

​ 程式1

elif [ 判斷條件2 ]

​ then

​ 程式2

else

​ 程式3

fi


說明:條件判斷語句中,注意前中括號與if之間必須有空格,中括號前後也必須都有空格

案例:

從鍵盤輸入年齡,根據年齡返回不同的形容語言

[root@heimatengyun test]# vim if.sh
#!/bin/bash
read -p "請輸入您的年齡:" AGE
if [ $AGE -le 18 ];then
      echo "未成年"
  elif [ $AGE -le 30 ];then
      echo "年輕氣盛"
  else
      echo "糟老頭子"
fi

使用vim編輯器編輯內容(此處使用vim是因為vim比vi更適合在程式設計環境使用,有錯誤提示等功能,建議編寫指令碼都採用vim),儲存退出並執行指令碼

[root@heimatengyun test]# bash if.sh 
請輸入您的年齡:16
未成年
[root@heimatengyun test]# bash if.sh 
請輸入您的年齡:40
糟老頭子

此處使用了read命令讀取鍵盤輸入,-p引數表示顯示提示符內容。

  • case語句

語法:


case $變數 in

​ " 值1")

​ 語句1

​ ;;

​ " 值2")

​ 語句2

​ ;;

​ *)

​ 語句

​ ;;

esac


案例:

根據傳入指令碼的引數分別輸出1、2、3對應的英文

[root@heimatengyun test]#vim case.sh
#!/bin/bash
case $1 in
  1)
    echo one
    ;;
  2)
    echo two
    ;;
  3)
    echo three
    ;;
  *)
    echo "error"
    ;;
esac

儲存退出並執行

[root@heimatengyun test]# bash case.sh 1
one
[root@heimatengyun test]# bash case.sh 2
two
[root@heimatengyun test]# bash case.sh 3
three
[root@heimatengyun test]# bash case.sh 8
error
2.4.2 迴圈語句

迴圈語句分為:for迴圈、while迴圈

  • for迴圈語句

for迴圈有兩種語法格式,分別如下:

語法1:


for 變數 in 值1 值2 值3...

​ do

​ 程式

​ done


語法2:


for ((初始值;迴圈控制條件;變數變化))

do

​ 程式

​ done


案例:

(1)一天三次問好

[root@heimatengyun test]#vim greeting.sh 
#!/bin/bash
for time in morning afternoon evening
  do
     echo "good $time"
  done

儲存並退出,執行問候指令碼,輸出問候語句

[root@heimatengyun test]# bash greeting.sh 
good morning
good afternoon
good evening

(2)用for迴圈求1累加到5的和

[root@heimatengyun test]# vim getsum.sh
#!/bin/bash
sum=0
for ((i=0;i<=5;i++))
   do
     sum=$(($i+$sum))
   done
echo "sum=$sum"

儲存退出,並執行指令碼

[root@heimatengyun test]# bash getsum.sh 
sum=15
  • while迴圈語句

語法:


while [ 條件判斷式 ]

​ do

​ 程式

​ done


案例:

(1)用while迴圈求1累加到5的和

[root@heimatengyun test]# vim while.sh  
#!/bin/bash
i=1
sum=0
while [ $i -le 5 ]
    do
     sum=$[$i+$sum]
     i=$[$i+1]
    done
echo "sum=$sum" 

儲存並執行

[root@heimatengyun test]# bash while.sh 
sum=15

注意:

1、是給變數賦值,左邊不用加$符號,比如i=$[$i+1]不能寫成$i=$[$i+1],否則報錯“command not found”

2、條件判斷語句注意左中括號與關鍵字之間、括號內部首位、操作符與運算元之間都有空格

3、i=$[$i+1],如果錯寫為i=$i+1則會報錯“integer expression expected”。

2.5 函式

函式是對功能的封裝,可以提供程式碼重用性。函式分為系統函式和使用者自定義函式,對於系統函式直接拿來使用即可,自定義函式則是根據具體需求編寫。

2.5.1 系統函式

使用現有的系統函式可以提高工作效率,由於篇幅所限,只簡單介紹兩個與檔案路徑相關的系統函式,具體使用方法也可以通過前面介紹的man命令來查詢函式具體的用法。

  • basename

語法:

​ basename [檔案路徑或字串] [字尾]

功能描述:

​ 刪掉所有的字首,包括最後一個/,然後列印字串;指定了字尾則在此基礎上再去掉字尾。

案例:

[root@heimatengyun test]# basename "sdf/sdf/sdf"
sdf
[root@heimatengyun test]# basename /root/test/test1.txt 
test1.txt
[root@heimatengyun test]# basename /root/test/test1.txt txt
test1.
[root@heimatengyun test]# basename /root/test/test1.txt .txt
test1
  • dirname

語法:

​ dirname 檔案絕對路徑

功能描述:

​ 從給定的包含絕對路徑的檔名中去除檔名,然後返回生效的路徑。簡單說就是去掉非目錄部分,返回保留目錄部分,但不包含最後的/

案例:

[root@heimatengyun test]# dirname /root/test/test1.txt    
/root/test
2.5.2 自定義函式

語法:


[function] 函式名[()]

{

​ 語句;

​ [return 返回值;]

}


說明:

(1)shell是解釋執行而非編譯執行,因此語句是逐行執行的,必須在函式呼叫之前先宣告函式。

(2)函式返回值只能通過$?系統變數獲取。可以顯示新增return語句返回(返回值範圍為0-255),否則將以最後一條命令執行結果作為返回值返回。

方括號內的部分function、引數、返回值可以是可選的。function show() 和function show和show()這幾種形式都是可以的。

案例:

(2)輸入1個整數,打印出比輸入小的整數

[root@heimatengyun test]# vim function.sh  
#!/bin/bash
function printNumber()
{
  i=0;
  while [ $i -lt $1 ]
  do
     echo $i;
     i=$[$i+1];
#    i=`expr \`expr $i + 1 \``     
     sleep 1;
  done
  return 0;
}
read -p "請輸入一個數:" n;
printNumber $n;

儲存檔案並執行指令碼

[root@heimatengyun test]# bash function.sh 
請輸入一個數:5
0
1
2
3
4

三、編寫Shell指令碼

通過上邊的演示,基本已經會編寫簡單的shell指令碼了,本小節講解shell指令碼接收使用者引數以及使用者引數的判斷。

3.1 接收使用者引數

之前提到過對linux命令來說,能否根據需要採用各種引數組合來完成特定的功能才是衡量命令是否掌握的標準。同樣,函式或指令碼也需要與使用者互動,能靈活處理使用者引數。

前面2.3中中已經提到幾個shell內設的用於接收引數的變數:$1...,$*,$@,$#

下面用案例進行演示

[root@heimatengyun test]# vim para.sh
#!/bin/bash
echo "當前指令碼名稱:$0"
echo "總共有$#個引數,分別為:$*"
echo "第一個引數為:$1,第三個引數為:$3"

儲存指令碼,並執行

[root@heimatengyun test]# bash para.sh 1 2 3
當前指令碼名稱:para.sh
總共有3個引數,分別為:1 2 3
第一個引數為:1,第三個引數為:3

可以看到,指令碼名稱後直接帶引數,引數之間用空格隔開,在指令碼內部直接可以獲取到各個位置上的引數

3.2 判斷使用者引數

shell指令碼中條件判斷語句可以用來判斷表示式是否成立,若條件成立返回0表示true,否則返回其他非0隨機數表示false。按判斷物件的不同,可以分為以下四種判斷語句:檔案測試語句、邏輯測試語句、整數值比較語句、字串比較語句。

3.2.1 檔案測試語句

檔案測試即是用指定的條件判斷檔案是否存在或許可權是否滿足,檔案測試具體引數如下

運算子 作用
-d 測試檔案是否為目錄型別
-e 測試檔案是否存在
-f 判斷是否為一般檔案
-r 測試當前使用者是否有讀許可權
-w 測試當前使用者是否有寫許可權
-x 測試當前使用者是否有執行許可權

案例:

判斷是否為目錄型別的檔案

[root@heimatengyun test]# [ -d /root/test ]
[root@heimatengyun test]# echo $?
0
[root@heimatengyun test]# [ -d /root/test/test1.txt ]
[root@heimatengyun test]# echo $?
1

先通過條件測試語句進行判斷,再使用shell直譯器內建的$?變數檢視執行結果。由於/root/test為目錄,所以返回0;test1.txt是檔案,所以返回1.

3.2.2 邏輯測試語句

邏輯語句用於對測試結果進行邏輯分析,根據測試結果可實現不同的效果。邏輯運算子如下:

運算子 作用
&& 邏輯與,表示當前面的命令執行成功後才會執行後邊的命令
|| 邏輯或,表示當前面的命令執行失敗後才會執行後邊的命令
邏輯非,表示把條件測試中判斷結果取反。

案例:

判斷當前登入使用者是否為管理員

[root@heimatengyun test]# [ ! $USER = root ] && echo "user" || echo "root"
root
3.2.3 整數值比較語句

整數比較運算子僅是對數字的操作,不能將數字與字串、檔案等內容一起操作,而且不能想當然地使用日常生活中的等號、大於號、小於號等來進行判斷。

ps:等號與賦值命令符衝突,大於小於號分別與輸出輸入重定向命令符衝突。因此一定要按照規範的整數比較運算子來進行操作。

可用的整數比較運算子如下表:

運算子 作用
-eq 是否等於
-ne 是否不等於
-gt 是否大於
-lt 是否小於
-le 是否小於或等於
-ge 是否大於或等於

案例:

[root@heimatengyun test]# [ 1 -lt 2 ]
[root@heimatengyun test]# echo $?    
0
[root@heimatengyun test]# [ 1 -gt 2 ]  
[root@heimatengyun test]# echo $?    
1
3.3.4 字串比較語句

字串比較語句用於判斷測試字串是否為空或兩個字串是否相等。常用來判斷某個變數是否未被定義,即內容為空值。字串常見運算子如下表:

運算子 作用
= 比較字串內容是否相同,相同為0
!= 比較字串內容是否不同,不同為0
-z 判斷字串內容是否為空,空則為0

案例:

分別檢視存在和不存在的變數,區別返回值

[root@heimatengyun test]# echo $USER
root
[root@heimatengyun test]# echo $TEST

[root@heimatengyun test]# [ -z $USER ]
[root@heimatengyun test]# echo $?
1
[root@heimatengyun test]# [ -z $TEST ]
[root@heimatengyun test]# echo $?     
0

至此,shell程式設計相關知識就介紹完畢,下一篇文章繼續學習與使用者和檔案、許可權相關的知識