初學者編寫bash腳本教程

初學者編寫bash腳本教程

bash shell script 定義

bash

bash是命令語言解釋器。廣泛用於各種gun/unix系統上的默認命令解釋器。全程叫做“Bourne-Again SHell”

shell

shell是一個宏處理器,允許執行交互式或非交互式的命令。

scripting

腳本允許自動執行,否則會一個接一個命令交互執行。

什麼是shell

shell允許你通過命令與計算機交互,從而檢索或存儲數據、處理信息和其它各種簡單甚至非常複雜的任務。

所以:你每天都會執行date cal pwd ls

什麼是scripting

script就是把上邊的4個命令放在一個文本里,然後一起執行。

bash是什麼

bash是許多GNU/Linux系統上的默認解釋器

查看默認解釋器執行命令:echo $SHELL

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

#! shebang,定義腳本的shell解釋器,寫在腳本的第一行如:

#!/bin/bash

通過which bash查找bash可執行二進制文件的完整路徑。

文件名和權限

要執行腳本,就要給腳本加執行的權限:chmod +x filename

通過file 命令查看文件的類型 file filename

[root@localhost ~]# file a.sh 
a.sh: Bourne-Again shell script, ASCII text executable
[root@localhost ~]# file epel-7.repo 
epel-7.repo: ASCII text

腳本執行

1、指定解釋器執行腳本

[root@localhost ~]# /bin/bash date.sh 
Sat Jul 14 09:01:39 UTC 2018

2、腳本文件頭定義shebang,文件加執行權限

[root@localhost ~]# cat date.sh 
#!/bin/bash

date
[root@localhost ~]# chmod +x date.sh 
[root@localhost ~]# ./date.sh 
Sat Jul 14 09:02:41 UTC 2018

相對路徑和絕對路徑

絕對路徑:

在linux中絕對路徑就是從根/開始,如:/usr/local/httpd

相對路徑:

就是路徑開頭沒有根/,如:cd abc/test/

路徑切換

cd 不加任何參數,切換到當前用戶home目錄。

cd ~ 都是切換到當前用戶home目錄。

cd 加路徑 切換到目標路徑,如:cd /usr/local

hello world 腳本

[root@localhost ~]# echo '#!/bin/bash' > hello.sh      
[root@localhost ~]# echo 'echo "hello world"' >> hello.sh 
[root@localhost ~]# cat hello.sh                     
#!/bin/bash
echo "hello world"
[root@localhost ~]# chmod +x hello.sh 
[root@localhost ~]# ./hello.sh 
hello world

簡單備份bash shell腳本

vi backup.sh    #創建腳本
# !/bin/bash
tar czf /tmp/root.tar /root/abc

chmod +x backup.sh  #加執行權限

./backup.sh #執行腳本

[root@localhost]# ls /tmp/root.tar  #驗證執行結果
/tmp/root.tar

常用變量

給變量賦值的不同方式

雙引號"" 允許用過$符號引用其他變量值

單引號'' 禁止引用其他變量值,$視爲普通字符

反撇號`` 將命令執行的記過輸出給變量;如:

sed -i s/"B"/"b"/g `grep -rl "B" --exclude="*.sql" ceshi/*`  將grep得到的文件傳輸給sed使用

用戶自定義變量

設置變量的作用範圍

格式:

export 變量名

export 變量名=變量值

清除變量名:

unset 變量名

自定義變量
[root@ceshi ~]# export a            #自定義變量a
[root@ceshi ~]# export b=222        #自定義變量b
[root@ceshi ~]# a=111               #給變量a賦值

[root@ceshi ~]# echo $a             #打印變量a的值
111
[root@ceshi ~]# echo $b             #打印變量b的值
222

清除變量:
[root@ceshi ~]# unset a             #清除變量a
[root@ceshi ~]# unset b             #清除變量b
[root@ceshi ~]# echo $a             #輸出爲空

[root@ceshi ~]# echo $b

環境變量

環境變量配置文件

  • 全局配置文件:/etc/profile

  • 用戶配置文件:~/.bash_profile

查看環境變量:

set 命令可以查看所有的shell變量,其中包括環境變量

常見的環境變量

  • $USER 查看當前用戶
  • $logname 登錄相關信息
  • $UID 當前用戶的UID,root用戶爲0
  • $SHELL 打印當前用戶的shell編輯器
  • $HOME 打印當前用戶的主目錄
  • $pwd 打印當前所在目錄
  • $PATH 用戶輸入的命令在哪裏目錄中查找
  • $PS1
  • $PS2
  • $RANDOM 顯示一個隨機數

位置變量

表示爲:$n (n爲1-9之間的數字)

#./test.sh one two three four five six
  • $0 表示腳本文件名本身

  • $1 表示one

  • $2 表示two

    依次類推

    $n 這個程式的第n個參數值,n=1..9

預定義變量

  1. $# 命令行中位置參數的個數
  2. $* 所有位置參數的內容
  3. $? 上一條命令執行後返回的狀態,爲0表示成功,爲非0表示執行異常或出錯
  4. $$ 當前所在進程的進程號
  5. $! 上一個指令PID 後臺運行的最後一個進程號
  6. $0 當前執行的進程/程序名
  7. $- 顯示shell使用的當前選項,與set命令功能相同
  8. $@ 跟$*類似,但是可以當作數組用

變量

變量是編程的本質。變量允許程序員在整個腳本中存儲、修改和重用數據。

創建一個新腳本welcome.sh:

#!/bin/bash

greeting="Welcome"
user=$(whoami)
day=$(date +%A)

echo "$greeting back $user! Today is $day."
echo "Your bash shell version is:$BASH_VERSION."

解釋腳本:

第一部分:

shebang 指定shell解釋器

greeting:定義一個變量,並賦值一個字符串值。

user:定義一個變量,賦值是通過一個命令替換技術來完成的。通過運行whoami獲取當前的用戶名,然後賦值給user。

day:定義一個變量,賦值同上;+%A代表只顯示周幾。

第二部分:

使用echo向終端打印信息,$greeting用來調用變量對應的值Welcome;$user同樣用來調用變量對應的用戶名;date也一樣。

注意:不要使用大寫命名私有變量,因爲大寫變量名都是內置變量。如果覆蓋內置變量,可能導致功能失調。

終端使用變量

[root@localhost ~]# a=5 #定義變量
[root@localhost ~]# b=6
[root@localhost ~]# echo $a #調用變量
5
[root@localhost ~]# echo $b
6
[root@localhost ~]# echo $[$a+$b] #變量算數
11

利用變量更新備份腳本,生成更有意義的文件名:

vi backup.sh

#!/bin/bash
# this bash script is backup a general user's home directory to /tmp/

user=$(whoami)
input=/home/$user
output=/tmp/${user}_home$(date +%Y-%m-%d_%H%M%S).tar.gz

tar czf $output $input
echo "Backup of $input completed!! Details about the output backup file:"
ls -l $output

腳本第五行:${parameter}叫參數展開,將花括號中的內容挨個和外面的內容結合。

[abc@localhost ~]$ ./backup.sh 
tar: Removing leading `/' from member names
Backup of /home/abc completed!! Details about the output backup file:
-rw-rw-r-- 1 abc abc 741 Jul 14 13:59 /tmp/abc_home2018-07-14_135933.tar.gz

輸入、輸出、錯誤重定向

名詞

通常在GNU/Linux命令行上執行的命令要麼產生輸出,要麼要求輸入,要麼拋出錯誤消息。這是shell腳本的基本概念,也是使用GNU/Linux命令行的基本概念。

每次執行命令時,可能會發生三種可能的結果。第一個場景是命令將產生一個預期的輸出,其次,該命令將生成一個錯誤,最後,您的命令可能根本不會產生任何輸出。

標準輸出如下:

終端輸出

[abc@localhost ~]$ ls backup.sh 
backup.sh

重定向輸出

重定向標識符:“>”

1、">" 輸出重定向

重定向會將 標準輸出 重定向到指定目標。

[abc@localhost ~]$ touch foobar 
[abc@localhost ~]$ ls foobar barfoo
ls: cannot access barfoo: No such file or directory
foobar
[abc@localhost ~]$ ls foobar barfoo > stdout.txt
ls: cannot access barfoo: No such file or directory
[abc@localhost ~]$ cat stdout.txt 
foobar

2、"2>" 錯誤輸出重定向

重定向將 標準錯誤 重定向到指定目標。

[abc@localhost ~]$ ls foobar barfoo 2> stderr.txt
foobar
[abc@localhost ~]$ cat stderr.txt 
ls: cannot access barfoo: No such file or directory

3、"&>" 輸出和錯誤 重定向 輸出

重定向將 標準輸出和標準錯誤 一塊重定向到指定目標。

[abc@localhost ~]$ ls foobar barfoo &> stdoutandstderr.txt
[abc@localhost ~]$ cat stdoutandstderr.txt 
ls: cannot access barfoo: No such file or directory
foobar

4、“>>” 追加重定向輸出

追加重定向輸出,不覆蓋文件,直接追加到文件結尾。

標準錯誤:

1、終端輸出

[abc@localhost ~]$ ls test.sh
ls: cannot access test.sh: No such file or directory

2、"2>" 錯誤輸出 重定向

重定向將 標準錯誤 重定向到指定目標。

[abc@localhost ~]$ ls foobar barfoo 2> stderr.txt
foobar
[abc@localhost ~]$ cat stderr.txt 
ls: cannot access barfoo: No such file or directory

標準輸入

終端輸入

通常終端輸入來自鍵盤,所以輸入的任何內容都稱爲stdin(標準輸入)。

文件輸入

接受來自文件的命令輸入。使用“<”符號接受文件輸入。

我們先用cat 命令,通過標準輸入然後,重定向到file.txt:

[abc@localhost ~]$ cat > file.txt
hello world.
welcome backup.
[abc@localhost ~]$ cat file.txt 
hello world.
welcome backup.

我們再用cat命令,通過< 符號輸入file.txt的內容:

[abc@localhost ~]$ cat < file.txt 
hello world.
welcome backup.

重定向輸入

1、“<” 輸入重定向

[root@localhost log]# cat >aaa.txt<<EOF
> hello world
> EOF
[root@localhost log]# cat aaa.txt 
hello world

2、“<<” 追加輸入重定向

[root@localhost log]# cat >>aaa.txt<<EOF
> 123456789
> HELLO WORLD
> EOF
[root@localhost log]# cat aaa.txt 
hello world
123456789
HELLO WORLD

read輸入

從鍵盤或文件中獲取標準輸入:read命令

語法:read 變量名

[abc@localhost ~]$ read a
abc
[abc@localhost ~]$ echo $a
abc
[abc@localhost ~]$ echo "the word you entered is: $a"   
the word you entered is: abc
[abc@localhost ~]$ read a1 a2 #同時輸入兩個字段
aaa bbb
[abc@localhost ~]$ echo $a1
aaa
[abc@localhost ~]$ echo $a2
bbb

詳細見:http://blog.51cto.com/506554897/2114407

backup.sh優化

之前的backup腳本執行時有個錯誤:

tar: Removing leading "/" from member names

消息告訴我們,絕對路徑已被刪除,從而提取了壓縮文件,而不是覆蓋任何現有文件。並沒有造成任何影響。

因此我可以利用重定向消除這種不必要的信息。通過重定向stderr到/dev/null,/dev/null作爲數據接收器,它丟棄任何重定向到它的數據,可以用man null查看詳細。

我們將腳本修改如下:

[abc@localhost ~]$ vi backup.sh 
#!/bin/bash
# this bash script is backup a general user's home directory to /tmp/

user=$(whoami)
input=/home/$user
output=/tmp/${user}_home$(date +%Y-%m-%d_%H%M%S).tar.gz

tar czf $output $input 2> /dev/null
echo "Backup of $input completed!! Details about the output backup file:"
ls -l $output
[abc@localhost ~]$ ./backup.sh 
Backup of /home/abc completed!! Details about the output backup file:
-rw-rw-r-- 1 abc abc 893 Jul 14 14:59 /tmp/abc_home2018-07-14_145919.tar.gz

執行後沒有了報錯信息。

函數

##名詞

函數允許程序員組織和重用代碼,從而提高了整個腳本的效率、執行速度和可讀性。

當注意到腳本包含兩行相同的代碼時,可以考慮使用一個函數。

函數是將不同命令的組號組合成當個命令的方法。如果所需的輸出或計算由多個命令組成,並且在整個腳本執行過程中需要多次,這可能非常有用。函數是使用函數關鍵字定義,後面是用花括號括起來的函數體。如:user_details {}

定義函數方法:

function 函數名{
  函數體(命令)
}
函數名() {
  函數體(命令)
}
函數名() 
{
  函數體(命令)
}

看個例子:

[abc@localhost ~]$ vi function.sh       
#!/bin/bash

function user_details {
    echo "User Name: $(whoami)"
    echo "User Home: $HOME"
}

user_details

詳細描述:

function是函數關鍵字,user_details是函數名。

花括號裏邊的是函數體,包括兩個echo命令,在兩個echo之前有縮進,縮進爲1個tab,4個空格。縮進可以擇自己的縮進方式。

最後的user_details 是在調用函數。

注意:函數定義必須在函數調用之前進行,否則腳本將返回stderr:command not found錯誤。

優化backup.sh

#!/bin/bash

# this bash script is backup a general user's home directory to /tmp/

user=$(whoami)
input=/home/$user
output=/tmp/${user}_home$(date +%Y-%m-%d_%H%M%S).tar.gz

function total_files {
    find $1 -type f | wc -l 
}

function total_directory {
    find $1 -type d | wc -l 
}

tar czf $output $input 2> /dev/null

echo -n "files to be included:"
total_files $input

echo -n "directory to be included:"
total_directory $input

echo "Backup of $input completed!!" 

echo "Details about the output backup file:" 
ls -l $output

代碼片段:

定義tottal_files函數,功能利用function find() { [native code] }和wc命令,來統計 提供給函數調用的目錄的文件數量。

定義total_diretory函數,功能利用function find() { [native code] }和wc命令,來統計 提供給函數調用的目錄的目錄數量。

執行腳本顯示如下:

數值和字符串比較

數字和字符串的基本比較運算符

描述 數值比較 字符串比較
少於 less than -lt <
大於 greater than -gt >
等於 equal -eq =
不等於 not equal -ne !=
小於或等於 -le N/A
大於或等於 -ge N/A
字符串不是空 N/A -n
字符串是空 N/A -z

N/A:代表不可用/不適用

-n $1:字符串$1不是空的

-z $1:字符串$1 爲空

當兩個數值比較後,可以用echo $?來檢查返回值,來查看結果是True 返回0;結果是False返回爲1。

數值比較

[abc@localhost ~]$ a=1
[abc@localhost ~]$ b=2
[abc@localhost ~]$ [ $a -lt $b ]
[abc@localhost ~]$ echo $?  #正確返回0
0
[abc@localhost ~]$ [ $a -gt $b ] 
[abc@localhost ~]$ echo $?  #不正確返回1
1
[abc@localhost ~]$ [ $a -eq $b ]  
[abc@localhost ~]$ echo $?      
1

字符串比較

[abc@localhost ~]$ [ "apple" = "banana" ]
[abc@localhost ~]$ echo $?
1
[abc@localhost ~]$ str1="apple"
[abc@localhost ~]$ str3="apple"
[abc@localhost ~]$ str2="banana"
[abc@localhost ~]$ [ $str1 = $str2 ]
[abc@localhost ~]$ echo $?
1
[abc@localhost ~]$ [ $str1 = $str3 ]
[abc@localhost ~]$ echo $?          
0

if /else/fi條件語句

名詞

條件允許程序員根據某些條件或事件在shell腳本中實現決策。

我們所指的條件語句是:if、then、else。

案例1-比較大小

舉個例子:if num1大於num2,then 就打印num1大於2,else 如果不大於,就打印其他信息。

[abc@localhost ~]$ vi num.sh
#!/bin/bash
num1=100
num2=200

if [ $num1 -gt $num2 ];then
    echo "$num1 greater than num2"
else
    echo "$num1 less than or equal $num2"
fi

注意fi 是關閉if條件塊的關鍵詞;注意中括號兩邊的間距,沒有空間,就不能執行。

[abc@localhost ~]$ chmod +x num.sh 
[abc@localhost ~]$ ./num.sh 
100 less than or equal 200

案例2-判斷目錄是否存在

查看當前目錄下是否有mysql目錄

[abc@localhost ~]$ vi mysql.sh 
#!/bin/bash

directory="./mysql"

if [ -d $directory ]; then
    echo "$directory is exist!!!"
else
    echo "$directory is not exist!"
fi

執行結果:

[abc@localhost ~]$ sh mysql.sh 
./mysql is not exist!
[abc@localhost ~]$ mkdir mysql
[abc@localhost ~]$ sh mysql.sh 
./mysql is exist!!!

案例3-嵌套if/else

[abc@localhost ~]$ cat nested.sh 
#!/bin/bash

# 這是一個嵌套if else腳本,while循環讓用戶循環選擇字段。

# 定義一個標誌
choise=0

# 定義一個函數,打印用戶可選擇的序號 字段
function p1() {
    echo "1. BeiJing"
    echo "2. ShangHai"
    echo "3. ChangSha"
    echo "請選擇你喜歡城市的序號[1-3]"
}

# 當choise爲0的時候就循環
while [ $choise -eq 0 ]; do
    p1
    read choise
    if [ $choise -eq 1 ]; then
        echo "你選擇的是:Beijing"
    else
        if [ $choise -eq 2 ]; then
            echo "您的選擇是: ShangHai"
        else
            if [ $choise -eq 3 ]; then
                echo "您的選擇是:ChangSha"
            else
                echo “請輸入合法的字段”
                choise=0
            fi
        fi
    fi
done

執行結果:

[abc@localhost ~]$ sh nested.sh 
1. BeiJing
2. ShangHai
3. ChangSha
請選擇你喜歡城市的序號[1-3]
1
你選擇的是:Beijing

[abc@localhost ~]$ sh nested.sh 
1. BeiJing
2. ShangHai
3. ChangSha
請選擇你喜歡城市的序號[1-3]
4
“請輸入合法的字段”
1. BeiJing
2. ShangHai
3. ChangSha
請選擇你喜歡城市的序號[1-3]
3
您的選擇是:ChangSha

[abc@localhost ~]$ sh nested.sh 
1. BeiJing
2. ShangHai
3. ChangSha
請選擇你喜歡城市的序號[1-3]
2
您的選擇是: ShangHai

案例4-if/elif.../else/fi

[abc@localhost ~]$ cat elif.sh 
#!/bin/bash

# 通過用戶輸入兩個數值,分別賦值給num1、num2,然後來比較大小
read num1 num2

if [ $num1 -eq $num2 ]; then
    echo "Both values are equal!"
elif [ $num1 -gt $num2 ]; then
    echo "num1 values is greater then num2"
else
    echo "num1 values is less then num2"
fi

執行結果:

[abc@localhost ~]$ sh elif.sh 
11 22
num1 values is less then num2
[abc@localhost ~]$ sh elif.sh 
11 11
Both values are equal!
[abc@localhost ~]$ sh elif.sh 
6 2
num1 values is greater then num2

優化backup.sh

執行完tar備份之後,要比較源數據的文件數量和備份後tar包的文件的數量。

#!/bin/bash

# this bash script is backup a general user's home directory to /tmp/

user=$(whoami)
input=/home/$user
output=/tmp/${user}_home$(date +%Y-%m-%d_%H%M%S).tar.gz

function total_files {
    find $1 -type f | wc -l 
}

function total_directory {
    find $1 -type d | wc -l
}

function total_archived_directory {
    tar -tzf $1 | grep /$ |wc -l
}

function total_archived_files {
    tar -tzf $1 | grep -v /$ | wc -l
}

tar czf $output $input 2> /dev/null

src_files=$( total_files $input )
src_directory=$( total_directory $input )

arch_files=$( total_archived_files $output )
arch_directory=$( total_archived_directory $output )

echo "Files to be include: $src_files"
echo "Directories to be include: $src_directory"
echo "Files archived: $arch_files"
echo "Directories archived: $arch_directory"

if [ $src_files -eq $arch_files ];then
    echo "Backup of $input completed!!!"
    echo "Details about the output backup file:"
    ls -l $output
else
    echo "Backup of $input Failed!!!"
fi  

bash文件測試

bash文件測試列表

指令 描述
-b filename 塊特殊文件
-c filename 特殊字符文件
-d directory name 檢查目錄是否存在
-e filename 檢查文件是否存在
-f filename 檢查是否存在常規文件,而不是目錄
-G filename 檢查文件是否存在,是否有效組ID
-g filename 如果文件存在並設置了組id,則爲true。
-k filename Sticky bit
-L filename 符號鏈接 Symbolic link
-O filename 如果文件存在並有效用戶id,則爲true。
-r filename 檢查文件是否可讀
-S filename 檢查文件是否爲socket
-s filename 檢查文件大小是否爲非零
-u filename Check if file set-ser-id bit is set
-w filename 檢查文件是否可寫
-x filename 檢查文件是否可執行

例子

[abc@localhost ~]$ vi file-test.sh
#!/bin/bash

# this is a test file exist ? yes/no

file="./testtest.txt"
if [ -e $file ]; then
    echo "File exists!"
else
    echo "File does not exists"
fi

腳本執行結果:

[abc@localhost ~]$ /bin/bash file-test.sh 
File does not exists
[abc@localhost ~]$ touch testtest.txt
[abc@localhost ~]$ /bin/bash file-test.sh 
File exists!

位置參數

名詞

到目前爲止,我們的備份腳本看起來很棒。我們可以計算結果壓縮備份文件中包含的文件和目錄的數量。此外,我們的腳本還促進了一個健全檢查,以確認所有文件已正確備份。缺點是我們總是被迫備份當前用戶的目錄。如果腳本足夠靈活,允許系統管理員僅通過將腳本指向主目錄就可以備份所選系統用戶的主目錄,那就太好了。

當使用bash位置參數時,這是一個相當容易的任務。位置參數通過命令行參數分配,並可在腳本中訪問,如$1, $2...$N變量。在腳本執行期間,在程序名稱之後提供的任何附加項都被視爲參數,並在腳本執行期間可用。

$、$# 、$*

考慮以下示例:

[abc@localhost ~]$which bash > position.sh 
[abc@localhost ~]$ vi position.sh 
#!/usr/bin/bash

echo $1 $2 $4
echo $#
echo $*
[abc@localhost ~]$ /bin/bash position.sh 1 2 3 4
1 2 4
4
1 2 3 4
[abc@localhost ~]$ /bin/bash position.sh one two three four
one two four
4
one two three four

腳本詳細:

#!/usr/bin/bash
echo $1 $2 $4
echo $#
echo $*

第二行代表打印:第一個、第二個、第四個 位置參數

第三行代表打印:$#提供參數的總和

第四行代表打印:$*提供的所有參數

優化backup.sh

使用位置參數知識,讓我們現在改進我們的backup.sh腳本來接受命令行中的參數。實現讓用戶決定哪個目錄將被備份。如果用戶在腳本執行期間沒有提交任何參數,默認情況下,該腳本將備份當前用戶的主目錄。新腳本如下:

#!/bin/bash 
# this bash script is used to backup a user's home directory 

if [ -z $1 ];then
    user=$(whoami)
else
    if [ ! -d "/home/$1" ]; then
        echo "Requested $1 user home directory doesn't exist!"
        exit 1
    fi
    user=$1
fi

# define default backup directory and tar.gz files
input=/home/$user
output=/tmp/${user}_home_$(date +%Y-%m-%d_%H%M%S).tar.gz

# count the number of source files
function total_files {
    find $1 -type f | wc -l 
}
# count the number of source directory
function total_directory {
    find $1 -type f | wc -l
}

# count the number of archived files
total_archived_files() {
    tar -tzf $1 | grep /$ | wc -l
}
# count the number of archived directory
total_archived_directory() {
    tar -tzf $1 | grep -v /$ | wc -l
}

tar -czf $output $input 2> /dev/null

src_files_num=$( total_files $input )
src_directory_num=$( total_directory $input)

arch_files_num=$( total_archived_files $output)
arch_directory_num=$( total_archived_directory $output)

echo "files to be included: $src_files_num"
echo "directory to be included: $src_directory_num"

echo "files to be included of archived: $arch_files_num"
echo "directory to be included of archived: $arch_directory_num"

if [ $src_files_num -eq $arch_files_num ]; then
    echo "Backup of $input complete!!"
    echo "Deatils about the output backup file:"
    ls -l $output
else
    echo "Backup of $input Failed!!!"
fi

腳本執行結果:

#不加參數執行,默認備份當前用戶主目錄
[abc@localhost ~]$ ./backup.sh 
files to be included: 21
directory to be included: 21
files to be included of archived: 1
directory to be included of archived: 21
Backup of /home/abc Failed!!!
#加參數指定用戶abc,備份abc用戶的主目錄
[abc@localhost ~]$ ./backup.sh abc
files to be included: 21
directory to be included: 21
files to be included of archived: 1
directory to be included of archived: 21
Backup of /home/abc Failed!!!
#加參數指定用戶aaa,aaa用戶不存在,報錯了。
[abc@localhost ~]$ ./backup.sh aaa
Requested aaa user home directory doesn't exist!

bash 循環

loop名詞

到目前爲止,本分腳本功能和可用性已經大大提高,通過位置參數我們可以指定備份 某個用戶的主目錄,我們可以輕鬆的備份任何用戶目錄。

當我們需要每天備份多個用戶目錄時,就會變得重複、乏味、費時。

這個時候我們可以用循環構造來迭代任何給定數量的任務。

有三種基本循環類型。

for循環

for循環用於迭代列表中任意數量的提供項的任何給定代碼。讓我們從一個簡單的for循環示例開始:

[abc@localhost ~]$ for i in 1 2 3; do echo $i; done
1
2
3

上邊的for循環使用了echo 命令打印所以項目。使用分號允許在當行執行for循環。將上面的for循環變成bash腳本,如下:

[abc@localhost ~]$ vi loop.sh        
#!/bin/bash

for i in 1 2 3; do
    echo $i
done
~       

for循環由四個shell保留字段組成:for、in、do、done。

因此,上述代碼可以理解爲:for 循環in在列表中的每一項,然後將每一項臨時賦值給變量i,然後doecho $i 打印到stdout,直到列表循環完才結束done

for循環例子

計算文本里邊每行的字符數量

[abc@localhost ~]$ cat items.txt 
abc
abcd
abcdef
a
[abc@localhost ~]$ for i in $( cat items.txt ); do echo -n $i | wc -c; done
3
4
6
1

while循環

while循環在給定的條件下工作。它將繼續執行封閉的代碼。當條件爲true的時候,do 執行代碼,當條件變爲false就終止done

[abc@localhost ~]$ vi while.sh
#!/bin/bash

counter=0
while [ $counter -lt 3 ]; do
    let counter+=1
    echo $counter
done

代碼詳解:

定義變量:counter

while定義條件:當變量counter小於3的時候,條件爲true。在每次循環迭代期間,使用let將counter自加1,然後打印變量counter。

done:當條件變成false 結束循環

腳本執行結果:

[abc@localhost ~]$ sh while.sh 
1
2
3

until循環

until循環和while循環完全相反的操作。利用until、do、done,循環直到條件從false變成true就結束。

[abc@localhost ~]$ vi until.sh
#!/bin/bash

counter=6
until [ $counter -lt 3 ]; do
    let counter-=1
    echo $counter
done

代碼詳解:

定義變量:counter

until定義條件:當變量counter不小於3的時候,條件爲false。在每次循環迭代期間,使用let將counter自加1,然後打印變量counter。

done:當條件變成true 結束循環

腳本執行結果:

[abc@localhost ~]$ sh until.sh 
5
4
3
2

優化backup.sh

之前的備份腳本每次執行備份一個用戶的目錄。我們可以通過位置參數給腳本提供多個用戶,通過for循環去執行備份命令。如下:

#!/bin/bash

# this bash script is used to backup one or more user's home directory

function backup {
    if [ ! -d "/home/$1" ]; then
        echo "resquested $1 user home directory does't exist."
        exit 1
    fi
    user=$1

    input=/home/$user
    output=/tmp/${user}_home_$(date +%Y-%m-%d_%H%M%S).tar.gz

    function total_files {
        find $1 -type f | wc -l
    }
    function total_directories {
        find $1 -type d | wc -l
    }

    function total_arch_files {
        tar -xvf $1 | grep -v /$ | wc -l    
    }
    function total_arch_directories {
        tar -xvf $1 | grep /$ | wc -l    
    }

    tar -czf $output $input 2> /dev/null

    # count the number of src_files and src_directory
    src_files=$( total_files $input )
    src_directories=$( total_directories $input )

    arch_files=$( total_arch_files $output )
    arch_directories=$( total_arch_directories $output )

    echo "######## $user ########"
    echo "Src_files to be included: $src_files"
    echo "Src_directories to be included: $src_directories"
    echo "Arch_files to be included: $arch_files"
    echo "Arch_directories to be included: $arch_directories"

    if [ $src_files -eq $arch_files ]; then
        echo "--------------------------------------"
        echo "Backup user:<$user> home completed!!!"
        echo "Details about the output backup files:"
        ls -l $output
    else
        echo "Backup user:<$user> home Failed!!!"
    fi
}

for directory in $*; do
    backup $directory
done; 

該腳本最好用root用戶去執行,因爲普通用戶備份別的用戶home目錄會存在權限問題。但該腳本並不能備份root的home目錄。腳本執行結果:

[root@localhost ~]# sh loop_backup.sh aaa bbb ccc
######## aaa ########
Src_files to be included: 4
Src_directories to be included: 1
Arch_files to be included: 4
Arch_directories to be included: 1
--------------------------------------
Backup user:<aaa> home completed!!!
Details about the output backup files:
-rw-r--r-- 1 root root 519 Jul 17 04:11 /tmp/aaa_home_2018-07-17_041134.tar.gz
######## bbb ########
Src_files to be included: 5
Src_directories to be included: 2
Arch_files to be included: 5
Arch_directories to be included: 2
--------------------------------------
Backup user:<bbb> home completed!!!
Details about the output backup files:
-rw-r--r-- 1 root root 573 Jul 17 04:11 /tmp/bbb_home_2018-07-17_041134.tar.gz
######## ccc ########
Src_files to be included: 6
Src_directories to be included: 3
Arch_files to be included: 6
Arch_directories to be included: 3
--------------------------------------
Backup user:<ccc> home completed!!!
Details about the output backup files:
-rw-r--r-- 1 root root 606 Jul 17 04:11 /tmp/ccc_home_2018-07-17_041134.tar.gz

select語句

select表達式是bash的一種擴展應用,擅長於交互式場合。

語法:

select var in ... ; do

    break;

done

例子:

[abc@localhost ~]$ vi select.sh 
#!/bin/bash

PS3="請選擇你最喜歡城市的序號:"

select word in "北京" "上海" "杭州" "長沙"; do
    echo "您選擇的是:$word"
    break
done

執行結果:

[abc@localhost ~]$ sh select.sh 
1) 北京
2) 上海
3) 杭州
4) 長沙
請選擇你最喜歡城市的序號:1
您選擇的是:北京

case語句

case語句爲多選擇語句。可以用case語句來匹配一個值與一個模式,如果匹配成功,執行相匹配的命令。

case語法:

case 值 in
模式1)
    command1
    command2
    ...
    ;;  # 兩個分號代表結束
模式2)
    command1
    command2
    ...
    ;;
esac

如上:case的值後面必須是in,每個模式必須以 右括號結束,取值可以是變量或常數。匹配某一模式後,就會執行模式下的所有命令 直到 ;; 。

取值一旦匹配了某個模式,就不會再去匹配其他模式。如果沒有匹配到,就會用 * 捕獲該值,再執行後面的命令。

case語句最後要有一個結束標記:esac 。

例子:

[abc@localhost ~]$ cat case.sh 
#!/bin/bash

echo "請輸入你喜歡的城市:"
echo "1) 北京"
echo "2) 上海"
echo "3) 杭州"
echo "4) 長沙"

read num

case $num in
1) 
    echo "你選擇的是北京"
    echo 1
    ;;
2)
    echo "你選擇的是上海"
    echo 2
    ;;
3)
    echo “你選擇的是杭州”
    ;;
4)
    echo "你選擇的是長沙"
    ;;
*)
    echo "輸入錯誤!請選擇[1-4]城市對應的序號!"
    ;;
esac

執行結果:

[abc@localhost ~]$ sh case.sh 
請輸入你喜歡的城市:
1) 北京
2) 上海
3) 杭州
4) 長沙
1
你選擇的是北京
1
[abc@localhost ~]$ sh case.sh 
請輸入你喜歡的城市:
1) 北京
2) 上海
3) 杭州
4) 長沙
3
“你選擇的是杭州”
[abc@localhost ~]$ sh case.sh 
請輸入你喜歡的城市:
1) 北京
2) 上海
3) 杭州
4) 長沙
5
輸入錯誤!請選擇[1-4]城市對應的序號!

算術運算

整數運算

let運算命令

[root@ceshi ~]# vi let.sh
#!/bin/bash
num1=2
num2=3
let result=num1+num2
echo $result

運行:
[root@ceshi ~]# /bin/bash let.sh 
5
  1. 自加操作 let num1++

  2. 自減操作 let num1--

  3. 簡寫形式:let no+=10let ; let no-=20

    等同於:let no=no+10; let no=no-20

操作符[]運算方法

[root@ceshi ~]# vi fangkuohao.sh
#!/bin/bash
num1=2
num2=3
result=$[$num1+num2]
echo $result

運行:
[root@ceshi ~]# /bin/bash fangkuohao.sh 
5

注:使用方法和let相似,在[]中可以使用$前綴

(())運算方法

[root@ceshi ~]# vi xiaokuohao.sh
#!/bin/bash
n1=2
n2=3
result=$((n1+n2))
echo $result

expr運算方法

[root@ceshi ~]# expr 2 + 3         
5
[root@ceshi ~]# num1=5
[root@ceshi ~]# r=$(expr $num1 + 5)
[root@ceshi ~]# echo $num1
5
[root@ceshi ~]# echo $r
10

expr的常用運算符

  • +
  • -
  • *
  • /
  • % 取模運算,也叫取餘

精密計算

高級運算工具:bc

它可以執行浮點運算和一些高級函數

[root@ceshi ~]# echo "1.25*3" | bc
3.75

設定小數精度(也就是小數點顯示幾位)

scale=2 代表小數點顯示2位

[root@ceshi ~]# echo "scale=2;7/3" | bc  
2.33

進制轉換

十進制轉二進制:
[root@ceshi ~]# a=192
[root@ceshi ~]# echo "obase=2;$a" |bc
11000000

二進制轉十進制:
[root@ceshi ~]# b=11000000                   
[root@ceshi ~]# echo "obase=10;ibase=2;$b"|bc
192

計算平方和平方根

求2的三次方:
[root@ceshi ~]# echo "2^3"|bc  
8

求100的平方根
[root@ceshi ~]# echo "sqrt(100)"|bc
10

trap 命令

用於指定在接收到信號後將要採取的動作,常見的用途是在腳本程序被中斷時完成清理工作、忽略ctrl+c。

當腳本在執行的時候,在終端駛入ctrl+c,trap會接收到消息,並執行相關trap命令。如下:

[abc@localhost ~]$ cat traptrap.sh 
#!/bin/bash

# difine bash trap command
trap bashtrap INT

# clear screen command
clear;

# define trap function:bashtrap, bashstrap is executed when CTRL-C is pressed;
bashtrap()
{
    echo "bash trap detected "CTRL+C" when script is executed. "
}

# for loop from 1/10 to 10/10
for a in `seq 1 10`; do
    echo "$a/10 to exit"
    sleep 1;
done

echo "exit bash trap example!"

當腳本在執行的時候,在終端輸入ctrl+c,trap到之後會執行bashtrap函數。執行結果如下:

[abc@localhost ~]$ sh traptrap.sh 
1/10 to exit
2/10 to exit
^Cbash trap detected CTRL+C when script is executed. 
3/10 to exit
^Cbash trap detected CTRL+C when script is executed. 
4/10 to exit
^Cbash trap detected CTRL+C when script is executed. 
5/10 to exit
6/10 to exit
7/10 to exit
^Cbash trap detected CTRL+C when script is executed. 
8/10 to exit
9/10 to exit
10/10 to exit
exit bash trap example!

數組

詳見:第四章 數組、關聯數組和別名使用

http://blog.51cto.com/506554897/2114414

聲明簡單bash數組

[abc@localhost ~]$ vi array.sh 
#!/bin/bash

# 聲明數組array,並賦值3個元素
array=( "Debian linux" "redhat linux" "ubuntu linux")

# 獲得數組array裏邊有多少個元素
elements=${#array[@]}

# 通過for循環數組裏邊的元素的索引,並通過數組索引打印每個元素
for (( i=0;i<$elements;i++)); do
    echo ${array[${i}]}
done

執行結果:

[abc@localhost ~]$ sh array.sh 
Debian linux
redhat linux
ubuntu linux

將文件讀入bash數組

[abc@localhost ~]$ cat read.sh 
#!/bin/bash

# 這是一個讀取文件每行內容到數組的腳本。

# 聲明一個變量array
declare -a array

# 用stdin鏈接文件記錄器10
exec 10<&0

# 將stdin替換爲作爲第一個參數提供的文件。
exec < $1

# 用while循環將文件每行內容作爲一個元素寫入數組
# count用作數組的下標
let count=0 
while read line; do
    echo "line text is: $line"
    array[$count]=$line
    ((count++))
done

# 打印數組的元素總個數,@和* 這裏是一個意思 都是所有
echo "count the number of array elements: ${#array[@]}"
# 打印數組的所有元素
echo "array content is: ${array[*]}"

# 從filedescriptor 10恢復stdin; filedescriptor:文件記錄器
# 關閉filedescriptor 10
exec 0<&10 10<&-

declare 聲明變量和顯示變量 詳見:第二十八章 聲明和顯示shell變量:declare命令

執行結果:

[abc@localhost ~]$ cat a.txt 
welcome
to
china
[abc@localhost ~]$ sh read.sh a.txt 
line text is: welcome
line text is: to
line text is: china
count the number of array elements: 3
array content is: welcome to china
[abc@localhost ~]$ cat b.txt 
大母雞 小母雞
母雞 母雞
老母雞
[abc@localhost ~]$ sh read.sh b.txt 
line text is: 大母雞 小母雞
line text is: 母雞 母雞
line text is: 老母雞
count the number of array elements: 3
array content is: 大母雞 小母雞 母雞 母雞 老母雞

bash 引用-轉義

轉義元字符

\ 反斜槓就是轉義特殊符號。

轉義元字符就是抑制元字符的特殊含義,因此元字符將被bash逐字逐句的閱讀。

語法:\特殊字符

[abc@localhost ~]$ vi escape.sh 
#!/bin/bash 

var1="Bash Script"

echo "$var1"
echo "\$var1"

執行結果:

[abc@localhost ~]$ sh escape.sh 
Bash Script
$var1

單引號

bash中的單引號 抑制每個元字符的特殊含義,元字符將被逐字逐句讀取。

即使單引號被反斜槓轉義,也不可能在兩個單引號中使用另一單引號。

[abc@localhost ~]$ vi quotes.sh
#!/bin/bash

var2="Bash Script"

echo "$var2"

echo '$var2 "$var2"'

執行結果:

[abc@localhost ~]$ sh quotes.sh 
Bash Script
$var2 "$var2"

雙引號

雙引號將抑制除了 `$ \ `` 以外的每個元字符的特殊含義。

任何其他元字符都會被逐字逐句讀取。

在雙引號中可以使用單引號。如果需要在雙引號中使用雙引號,可以使用"\" 進行轉義。

[abc@localhost ~]$ vi double-quote.sh 
#!/bin/bash

var3="Hello world"

echo "$var3"

echo "這是一個打招呼的詞語:$var3"
echo "這是一個到招呼的詞語: \"$var3\", time is `date +"%Y-%m-%d_%H:%M:%S"` "

執行結果如下:

[abc@localhost ~]$ sh double-quote.sh 
Hello world
這是一個打招呼的詞語:Hello world
這是一個到招呼的詞語: "Hello world", time is 2018-07-19_16:35:23 

ANSI-C 類型 引用

ANSI-C :ANSI C是由美國國家標準協會(ANSI)及國際標準化組織(ISO)推出的關於C語言的標準。

在ANSI-C 類型的引用中,用“\” 轉義的字符將按照ANSI-C標準獲得特殊的含義。

轉義字符 描述
\a alert 警報
\b backspace 退格鍵
\e 一個轉義字符
\f form feed 換頁符
\n newline 換行
\r carriage return 回車
\t horizontal tab 水平製表符
\v vertical tab 垂直製表符
\\ 反斜槓
\' 單引號
\nnn 字符的八進制值(見[http:/www.asciitable.com/ASCII表])
\xnn 字符的十六進制值(見[http:/www.asciitable.com/ASCII表])

ANSI-C 引用語法:$‘

[abc@localhost ~]$ vi ansic.sh 
#!/bin/bash

echo $'http://www.abc.com\nmai\x40abc.com'

[root@docker-2 ~]# vi a.sh 
#!/bin/bash

echo $'http://www.baidu.com\nmail\x40baidu.com'

執行結果:

[abc@localhost ~]$ sh ansic.sh 
http://www.abc.com
[email protected]

[root@docker-2 ~]# sh a.sh 
http://www.baidu.com
[email protected]
本教程pdf下載地址:http://down.51cto.com/data/2451862
下載內容包含筆者29章節的 shell學習筆記
也可參考shell鏈接:http://tldp.org/LDP/abs/html/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章