Shell 腳本入門及語法速查

1. Hello World

1.1. 基本結構

建立 helloWorld.sh 文件,寫入以下內容:shell

#!/bin/bash

echo "hello world"

其中 #! 告訴系統其後路徑所指定的程序是解釋此腳本文件的 Shell 程序,常見的 Shell 程序有如下幾類(可經過命令 cat /etc/shells 查看):apache

  • Bourne Shell(/usr/bin/sh或/bin/sh)
  • Bourne Again Shell(/bin/bash)
  • C Shell(/usr/bin/csh)
  • K Shell(/usr/bin/ksh)
  • Shell for Root(/sbin/sh)
  • ……

其中 Bash 在平常工做中被普遍使用,同時也是大多數 Linux 系統默認的 Shell。數組

執行該 sh 腳本bash

# 增長可執行權限
➜  chmod u+x helloWorld.sh

# 運行腳本
➜  ./helloWorld.sh
或
➜  sh hellowWorld.sh

1.2. 註釋

單行註釋函數

  • # 開頭的行是註釋

多行註釋測試

  • 方式一:用一對 {} 括起來,定義成一個函數,沒有地方調用即達到註釋的效果。
  • 方式二:命令行

    :<<EOF
    註釋內容...
    註釋內容...
    註釋內容...
    EOF

2. 基本語法

2.1. 變量

  • 變量定義日誌

    • 變量名建議大寫;
    • 有效字符僅能包含字母、數字、下劃線,首個字符不能以數字開頭;
    • = 兩邊不能有空格;
    • 不能使用標點符號;
    • 不能使用 bash 裏的關鍵字(可用 help 命令查看保留關鍵字)。
    # 示例
    VAR1="whoru"
    VAR2=100
    var3=/data/www
    var4_name="root"
  • 訪問變量 $VAR1$(var1),其中,加花括號是爲了幫助解釋器識別變量的邊界。
  • 設置變量只讀 readonly VAR1
  • 刪除變量(不適用於只讀變量!unset VAR1
  • 局部、全局變量code

    • 不作特殊聲明,shell 中全部變量都是全局變量
    • 可使用關鍵字 local 定義局部變量。
    • 若是函數內部和外部存在同名變量,則內部會覆蓋外部

2.2. 字符串

  • 值用雙引號 "" 或單引號 '' 表示orm

    • 單引號單限制:

      • 單引號裏的任何字符都會原樣輸出;
      • 單引號字符串中的變量是無效的;
    • 雙引號的優勢:

      • 雙引號裏能夠有變量;
      • 雙引號裏能夠出現轉義字符;
  • 其它

    # 字符串拼接
    name="xiaoming"
    var2="hello, "$name # 輸出 hello, xiaoming
    
    # 獲取字符串長度
    string="abcd"
    echo ${#string} # 輸出 4
    echo `expr length "$string"` # 輸出 4
    
    # 提取子字符串
    msg="zhangsan is a good man"
    echo ${msg:1:4} # 輸出 hang
    echo ${msg: -3} # 輸出 man

2.3. 數組

  • bash 支持一維數組(不支持多維數組),而且沒有限定數組的大小。
  • 數組元素的下標由 0 開始,獲取數組元素要用到下標。
  • 定義:

    array1=(value0 value1 value2 value3)
    
    # 或
    
    array2[0]=value0
    array2[1]=value1
    array2[2]=value2
  • 讀取

    # 指定下標的元素
    ➜  echo ${array2[2]}; // 輸出 value2
    
    # 獲取數組全部元素
    ➜  echo ${array2[*]}; // 輸出 value0 value1 value2
    ➜  echo ${array2[@]}
  • 獲取數組元素個數

    ➜  echo ${#array2[@]}; // 輸出 3
    ➜  echo ${#array2[*]};
  • 取得數組中指定下標元素的字符長度

    ➜  echo ${#array2[2]};

2.4. 傳遞參數

在執行 Shell 腳本時,能夠向腳本傳遞參數,腳本內獲取參數的格式爲 $n,這裏的 n 指傳遞給腳本的第 n 個參數。

以下腳本文件 demo.sh

#!/bin/bash

echo "執行的文件名:$0";
echo "第一個參數爲:$1";
echo "第二個參數爲:$2";
echo "第三個參數爲:$3";

執行該文件,並傳遞參數,以下:

➜  ./demo3.sh param1 param2 param3
執行的文件名:./demo3.sh
第一個參數爲:param1
第二個參數爲:param2
第三個參數爲:param3

其中,$0 是一個特殊變量,表明當前腳本文件名,還有幾個相似的變量以下:

變量 說明
$# 傳遞給腳本的參數個數。
$* 以一個單字符串的形式顯示全部向腳本傳遞的參數,如 "$1 $2 ... $n"
$@ $* 相同,可是使用引號把每一個參數包裹起來,如 "$1" "$2" ... "$n"
$? 最後一個執行的命令的退出狀態:0 正常;1 或其它任何值,表示有錯誤
$$ 腳本運行的當前進程ID號
$! 最後一個後臺命令的進程號。

3. 運算符

3.1. 算數運算符

原生 bash 不支持簡單的數學運算,可是能夠經過其餘命令來實現,例如 awk 和 expr,其中 expr 最經常使用。

假定有兩個變量:a=10 b=20

運算符 說明 舉例
+ 加法 `expr $a + $b` 結果爲 30。
- 減法 `expr $a - $b` 結果爲 -10。
* 乘法 `expr $a \* $b` 結果爲 200。
/ 除法 `expr $b / $a` 結果爲 2。
% 取餘 `expr $b % $a` 結果爲 0。
= 賦值 a=$b 將把變量 b 的值賦給 a。
== 用於比較兩個數字是否相同 [ $a == $b ] 返回 false。
!= 用於比較兩個數字是否不相同 [ $a != $b ] 返回 true。

注意

  • 表達式和運算符之間要有空格,如 2+2 是錯誤的,必須寫成 2 + 2;
  • 完整的表達式要被反引號 ` ` 包裹起來;

3.2. 關係運算符

關係運算符只支持數字,不支持字符串,除非字符串的值是數字。

假定有兩個變量:a=10 b=20

運算符 說明 舉例
-eq 檢測兩個數是否相等 [ $a -eq $b ] 返回 false。
-ne 檢測兩個數是否不相等 [ $a -ne $b ] 返回 true。
-gt 檢測左邊的數是否大於右邊的 [ $a -gt $b ] 返回 false。
-lt 檢測左邊的數是否小於右邊的 [ $a -lt $b ] 返回 true。
-ge 檢測左邊的數是否大於等於右邊的 [ $a -ge $b ] 返回 false。
-le 檢測左邊的數是否小於等於右邊的 [ $a -le $b ] 返回 true。

3.3. 布爾操做符

假定有兩個變量:a=10 b=20

運算符 說明 舉例
! 非運算,表達式爲 true 則返回 false,不然返回 true。 [ ! false ] 返回 true。
-o 或運算,有一個表達式爲 true 則返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a 與運算,兩個表達式都爲 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false。

3.4. 邏輯運算符

假定有兩個變量:a=10 b=20

運算符 說明 舉例
&& 邏輯的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false
|| 邏輯的 OR [[ $a -lt 100 &#124;&#124; $b -gt 100 ]] 返回 true

3.5. 字符串運算符

假定有兩個變量:a="abc" b="efg"

運算符 說明 舉例
= 檢測兩個字符串是否相等 [ $a = $b ] 返回 false。
!= 檢測兩個字符串是否不相等 [ $a != $b ] 返回 true。
-z 檢測字符串長度是否爲 0(空) [ -z $a ] 返回 false。
-n 檢測字符串長度是否不爲0(非空) [ -n "$a" ] 返回 true。
str 檢測字符串是否爲不爲空 [ $a ] 返回 true。

3.6. 文件測試運算符

運算符 說明(若是是,則返回 true) 舉例
-b 檢測文件是不是塊設備文件 [ -b $file ]
-c 檢測文件是不是字符設備文件 [ -c $file ]
-d 檢測文件是不是目錄 [ -d $file ]
-f 檢測文件是不是普通文件(既不是目錄,也不是設備文件) [ -f $file ]
-g 檢測文件是否設置了 SGID 位 [ -g $file ]
-k 檢測文件是否設置了粘着位(Sticky Bit) [ -k $file ]
-p 檢測文件是不是有名管道 [ -p $file ]
-u 檢測文件是否設置了 SUID 位 [ -u $file ]
-r 檢測文件是否可讀 [ -r $file ]
-w 檢測文件是否可寫 [ -w $file ]
-x 檢測文件是否可執行 [ -x $file ]
-s 檢測文件是否爲非空(文件大小是否大於0)文件 [ -s $file ]
-e 檢測文件(包括目錄)是否存在 [ -e $file ]

4. 流程控制

4.1. if 語句

大多使用關係運算符檢查關係

# 語法格式
if condition1
then
    command1
    ...
elif condition2
then
    command2
else
    commandN
fi

4.2. case 語句

# 語法格式
case 值 in
    模式1)
        command1
        command2
        ...
        commandN
        ;;
    模式2)
        command1
        command2
        ...
        commandN
        ;;
    *)
        commandDefault
        ;;
esac

4.3. while 語句

用於不斷執行一系列命令,也用於從輸入文件中讀取數據;命令一般爲測試條件。其格式爲:

# 語法格式
while condition
do
    command
done

4.4. until 循環

執行一系列命令直至條件爲 true 時中止,它與 while 循環 在處理方式上恰好相反。

# 語法格式
until condition
do
    command
done

4.5. for 循環

# 語法格式
for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
done

4.6. 無限循環

# 語法1
while :
do
    command
done

# 語法2
while true
do
    command
done

# 語法3
for (( ; ; ))

4.7. 退出循環

  • break 跳出整個循環,執行循環體後面的代碼,支持 break n 退出多層嵌套循環
  • continue 結束當前循環,一樣支持 continue n 退出多層

5. 輸入、輸出重定向

5.1. 命令列表

命令 說明
command > file 將輸出結果重定向到 file。
command < file 將輸入重定向到 file。
command >> file 將輸出以追加的方式重定向到 file。
n > file 將文件描述符爲 n 的文件重定向到 file。
n >> file 將文件描述符爲 n 的文件以追加的方式重定向到 file。
n >& m 將輸出文件 m 和 n 合併。
n <& m 將輸入文件 m 和 n 合併。
<< tag 將開始標記 tag 和結束標記 tag 之間的內容做爲輸入。

關於文件描述符:

  • 0 一般是標準輸入(STDIN),Unix程序默認從 stdin 讀取數據。
  • 1 標準輸出(STDOUT),Unix程序默認向 stdout 輸出數據。
  • 2 標準錯誤輸出(STDERR),Unix程序會向 stderr 流中寫入錯誤信息。

示例:

# 將 stdout 和 stderr 合併後重定向到 file
➜  command > file 2>&1

5.2. /dev/null 文件

這是一個特殊的文件,寫入到它的內容都會被丟棄;若是嘗試從該文件讀取內容,也什麼也讀不到。咱們一般將命令的輸出重定向到它,起到「禁止輸出」的效果。

如:

# 屏蔽 stdout 和 stderr
➜  command > /dev/null 2>&1

5.3. Here 文檔

# 將兩個 delimiter 之間的內容(document) 做爲輸入傳遞給 command。
command << delimiter
document
delimiter

說明:

  • 結尾的 delimiter 必定要頂格寫,前面不能有任何字符,後面也不能有任何字符,包括空格和 tab 縮進。
  • 開始的 delimiter 先後的空格會被忽略掉。

6. 函數

6.1. 基本語法

[ function ] funcName [()] {

    command;

    [return int;]

}

說明:

  • function 關鍵字非必須;
  • 若是該函數不傳入變量,這函數名的後面的括號能夠不加;
  • return 函數返回值

    • 非必須,默認返回最後一條命令的執行結果;
    • 它只能返回 1 ~ 255 之間的整數,一般只是用來供其它地方獲取狀態,好比 0 成功,1 或 非 0 失敗;
    • 也可使用 echo 輸出一個字符串做爲函數的返回值。
  • 調用函數僅使用其函數名,如 funcName
  • 全部函數在使用前必須定義,即函數調用必需要在函數聲明以後。

6.2. 函數參數

func() {
    echo "第一個參數爲 $1 !"
    echo "第二個參數爲 $2 !"
    ...
    echo "第十個參數爲 ${10} !"
    ...
}

# 調用並傳參
func param1 param2 param3

說明

  • 在函數體內部,經過 $n 的形式來獲取參數的值,例如:$1 表示第一個參數,$2 表示第二個參數;
  • 當 n >= 10 時,須要使用 ${n} 來獲取參數。

7. 包含文件(封裝函數庫)

一般咱們將公用的函數抽離到單獨文件,以便重複調用,減小冗餘代碼。

對於一個函數庫文件:

  • 後綴名任意,一般使用 .lib 進行標識;
  • 通常不授予可執行權限;
  • 不須要跟腳本放在同一級目錄,只需在腳本引用時指定;
  • 一般第一行通常使用 #!/bin/echo 輸出警告信息,避免用戶執行。

示例:

#!/bin/echo
# /home/user1/lib/comm_function.lib

function add {
    echo "`expr $1 + $2`"
}
#!/bin/bash
# /home/user1/test.sh

# 引入函數庫文件
# 使用絕對 或 相對路徑
. ./lib/comm_function.lib

# 使用文件中的函數
add 1 3
➜  sh -x test_functions.sh
+ . ./lib/comm_function.lib
+ add 1 3
++ expr 1 + 3
+ echo 4
4

8. 經常使用命令

8.1. find 命令

語法:find [路徑] [選項] [操做]

選項

選項 說明 選項 說明
-name 文件名 -iname 文件名(忽略大小寫)
-perm 777 文件權限 -type f|d|l|c|b|p 文件類型
-user 文件屬主 -nouser 無有效屬主
-group 文件屬組 -nogroup 無有效屬組
-size -n|+n 文件大小 -prune 排除某些查找目錄(一般與 -path 一同使用)
-mindepth n 從 n 級子目錄開始查找 -maxdepth n 最多搜索到 n 級子目錄
-mtime -n|+n 文件修改時間(天) -mmin -n|+n 文件修改時間(分鐘)
-newer file1 文件修改時間比 file1 早

示例:

# 文件名
➜  find /etc/ -name '*.conf'

# 文件類型
# f 文件;d 目錄;c 字符設備文件;
# b 塊設備文件;l 連接文件;p 管道文件
➜  find /etc/ -type f

# 文件大小
# -n 小於等於;+n 大於等於
➜  find . -size +100M
➜  find . -size -10k

# 文件修改時間
# -n < n天之內修改過的文件;
# n = n 天修改過得文件;
# +n > n天之外修改過的文件;
➜  find . -mtime -3
➜  find . -mtime 3
➜  find . -mtime +3

# 排除目錄
# -path ./test1 -prune 排除 test1 目錄
# -path ./test2 -prune 排除 test2 目錄
# -o type f 固定結尾寫法
➜  find . -path ./test1 -prune -o -path ./test2 -prune -o type f

操做

  • -print 打印輸出
  • -exec 'command' {} \; 其中 {} 是前面查找匹配到的結果
  • -ok 與 exec 功能同樣,但每次操做都給用戶提示,由用戶決定是否執行對應的操做。

示例:

# 查找 30 天之前的日誌文件並刪除
➜  find /var/log -name '*.log' -mtime +30 -exec rm -f {} \;

# 查找全部 .conf 文件,並移動到指定目錄
➜  find /etc/apache -name '*.conf' -exec cp {} /home/user1/backup \;

8.2. echo 命令

用於字符串的輸出,基本格式 echo string

使用示例:

# 顯示普通字符
➜  echo "It is a test" # 輸出 It is a test

# 顯示轉義字符
➜  echo "\"It is a test\"" # 輸出 "It is a test"

# 顯示變量
#!/bin/sh
NAME="xiaoming"
➜  echo "$NAME It is a test" # 輸出 xiaoming is a test

# 顯示換行
➜  echo -e "OK! \n" # -e 開啓轉義
➜  echo "It is a test"

# 顯示不換行
➜  echo -e "OK! \c" # -e 開啓轉義 \c 不換行
➜  echo "It is a test"

# 顯示結果定向至文件
➜  echo "It is a test" > myfile

# 顯示命令執行結果
➜  echo `date`

8.3. printf 命令

模仿 C 程序庫(library)裏的 printf() 程序,主要用於格式化輸出。

默認 printf 不會像 echo 自動添加換行符,咱們能夠手動添加 \n

其基本語法格式爲:

➜  printf  format-string  [arguments...]

說明:

  • format-string 爲格式控制字符串
  • arguments 爲參數列表。

示例:

➜  printf "%-10s %-8s %-4s\n" 姓名 性別 體重kg
➜  printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234
➜  printf "%-10s %-8s %-4.2f\n" 楊過 男 48.6543
➜  printf "%-10s %-8s %-4.2f\n" 郭芙 女 47.9876
姓名     性別   體重kg
郭靖     男      66.12
楊過     男      48.65
郭芙     女      47.99

其中:

  • %s %c %d %f 都是格式替代符;
  • %-10s 指一個寬度爲10個字符(-表示左對齊,沒有則表示右對齊),任何字符都會被顯示在10個字符寬的字符內,若是不足則自動以空格填充,超過也會將內容所有顯示出來。
  • %-4.2f 指格式化爲小數,其中.2指保留2位小數。

更多使用示例:

# 沒有引號也能夠輸出
➜  printf %s abcdef

# 格式只指定了一個參數,但多出的參數仍然會按照該格式輸出,format-string 被重用
➜  printf %s abc def
abcdef

➜  printf "%s\n" abc def
abc
def

# 若是沒有 arguments,那麼 %s 用NULL代替,%d 用 0 代替
➜  printf "%s and %d \n"
 and 0

8.4. test 命令

用於檢查某個條件是否成立,它能夠進行數值、字符和文件三個方面的測試(詳見第3節對應的運算符部分)。

基本使用示例:

cd /bin
if test -e ./bash
then
    echo '文件已存在!'
else
    echo '文件不存在!'
fi

9. 補充

9.1. 變量替換

規則 說明 示例 var="Hello shell"
${變量#匹配規則} 從頭開始匹配,最短刪除 ${var#*e} => llo shell
${變量##匹配規則} 從頭開始匹配,最長刪除 ${var##*e} => ll
${變量%匹配規則} 從尾開始匹配,最短刪除 ${var%e*} => Hello sh
${變量%%匹配規則} 從尾開始匹配,最長刪除 ${var%%e*} => H
${變量/舊字符串/新字符串} 只替換匹配到的第一個 ${var/e/*} => H*llo shell
${變量//舊字符串/新字符串} 所有替換 ${var//e/*} => H*llo sh*ll

9.2. 有類型變量

shell 中變量默認都是字符串,除非使用如下方式聲明。

declare 或 typeset 參數 說明
-r 只讀
-i 整數
-a 數組
-f 在腳本中顯示定義的函數和內容
-F 在腳本中顯示定義的函數
-X 將變量聲明爲環境變量

示例:

➜  declare -r var1="hello shell type"
➜  var1="hello lalala"
zsh: read-only variable: var1

9.3. 使用 bc 進行浮點數運算

系統內置,支持 +-*/^ 指數% 取餘,並使用 scale 指定小數位數,默認 0

示例:

➜  which bc
/usr/bin/bc

# 示例
➜  echo "5+4" | bc
9
➜  echo "5-4" | bc
1
➜  echo "5*4" | bc
20
➜  echo "5/4" | bc
1
➜  echo "scale=3;5/4" | bc
1.250
➜  echo "5%4" | bc
1
➜  echo "5^4" | bc
625

9.4. [ ... ] 與 [[ ... ]]

  • [[ 是關鍵字,許多 shell 並不支持這種方式。

    • 全部的字符都不會被文件擴展或是標記分割,可是會有參數引用和命令替換;
    • 更能防止腳本里的許多邏輯錯誤,好比說 &&, ||, <> 操做符能在一個 [[ ... ]] 測試裏經過,但在 [ ... ] 結構會發生錯誤。
    • 會進行算術擴展。
  • [ 是一條命令,與 test 等價,大多數的 shell 都支持。

    • 在其中的表達式應是它的命令行參數,因此串比較操做符 >< 必須轉義,不然就變成 IO 重定向操做符了。
    • 不會進行算術擴展。