Bash 指令碼是 Linux 系統管理和自動化任務的重要工具。它將一系列命令列指令整合到一個檔案中,方便重複執行和管理。本文從命令列的基礎開始,逐步深入講解 Bash 指令碼的各個方面,包括變數、引數處理、條件判斷、迴圈控制、函式以及模式匹配等,並提供豐富的程式碼範例,幫助讀者理解和應用。對於想要提升 Linux 系統操作效率和自動化能力的工程師來說,Bash 指令碼的學習至關重要,本文提供了一個入門,幫助讀者快速上手並掌握 Bash 指令碼的核心概念和技巧。
從命令列到指令碼
shell 指令碼只是包含命令列指令的檔案。將一個或多個指令放入檔案中,就建立了一個 shell 指令碼。如果將檔案命名為 myscript,可以透過輸入 bash myscript 來執行該指令碼,或者賦予其執行許可權(例如,chmod 755 myscript),然後直接呼叫它來執行指令碼:./myscript。
通常在指令碼的第一行加入以下內容,以告訴作業系統所使用的指令碼語言:
#!/bin/bash -
或者,為了提高可移植性,可以使用:
#!/usr/bin/env bash
內容解密:
#!/bin/bash -:指定使用/bin/bash來解釋執行指令碼。#!/usr/bin/env bash:使用env命令查詢bash的位置,以提高可移植性。
練習題
編寫一個指令,執行
ifconfig並將標準輸出重新導向到名為ipaddress.txt的檔案。ifconfig > ipaddress.txt內容解密:
ifconfig:顯示或組態網路介面引數。> ipaddress.txt:將輸出重新導向到ipaddress.txt。
編寫一個指令,執行
ifconfig並將標準輸出附加到名為ipaddress.txt的檔案。ifconfig >> ipaddress.txt內容解密:
>>:將輸出附加到指設定檔案的末尾。
編寫一個指令,將
/etc/a目錄中的所有檔案複製到/etc/b目錄,並將標準錯誤重新導向到copyerror.log檔案。cp /etc/a/* /etc/b/ 2> copyerror.log內容解密:
cp /etc/a/* /etc/b/:將/etc/a中的所有檔案複製到/etc/b/。2>:將標準錯誤重新導向到指設定檔案。
編寫一個指令,對根目錄進行目錄列表(
ls),並將輸出透過管道傳輸到more命令。ls / | more內容解密:
ls /:列出根目錄下的檔案和目錄。|:管道符號,將前一個命令的輸出作為下一個命令的輸入。
編寫一個指令,執行
mytask.sh並將其放到背景。./mytask.sh &內容解密:
./mytask.sh:執行當前目錄下的mytask.sh指令碼。&:將任務放到背景執行。
給定以下任務列表,編寫指令將 Amazon ping 任務帶回前景:
[1] Running ping www.google.com > /dev/null & [2]- Running ping www.amazon.com > /dev/null & [3]+ Running ping www.example.com > /dev/null &fg 2內容解密:
fg %2:將任務編號 2 帶回前景繼續執行。
Bash 簡介
Bash 不僅是一個簡單的命令列介面,用於執行程式;它本身也是一種程式語言。預設情況下,它用於啟動其他程式。當命令列上出現多個單詞時,bash 假設第一個單詞是要啟動的程式名稱,其餘單詞是要傳遞給該程式的引數。
輸出資訊
與任何程式語言一樣,bash 能夠將資訊輸出到螢幕。可以使用 echo 命令實作輸出:
$ echo "Hello World"
Hello World
也可以使用 printf 內建命令,它允許額外的格式化功能:
$ printf "Hello World\n"
Hello World
內容解密:
echo "Hello World":輸出字串 “Hello World”。printf "Hello World\n":格式化輸出字串 “Hello World”,並在末尾新增換行符號。
如前一章所述,可以將輸出重新導向到檔案、stderr,或透過管道傳輸到另一個命令。接下來的章節中,將會看到更多這些命令及其選項的使用範例。
Bash 變數與引數處理
Bash 變數以字母或底線開頭,後面可接字母、數字或底線。預設情況下,這些變數被視為字串變數,除非特別宣告。指定給變數的方式如下:
MYVAR=textforavalue
要檢索變數的值,例如使用 echo 命令列印出來,需要在變數名稱前加上 $ 符號,像這樣:
echo $MYVAR
如果要將一系列單詞指定給變數,也就是保留空白字元,需要在值周圍使用引號,如下所示:
MYVAR='here is a longer set of words'
OTHRV="either double or single quotes will work"
使用雙引號會允許在字串內進行其他替換。例如:
firstvar=beginning
secondvr="this is just the $firstvar"
echo $secondvr
這將輸出 this is just the beginning。
內容解密:
- 變數指定:
MYVAR=textforavalue將字串textforavalue指定給MYVAR。 - 變數檢索:
echo $MYVAR透過$符號檢索並列印MYVAR的值。 - 引號的使用:單引號保留所有字元的字面意義,而雙引號允許變數替換。
- 命令替換:可以使用
$( )將命令的輸出儲存在變數中,例如CMDOUT=$(pwd)。
位置引數
在命令列工具中,通常透過引數傳遞資料到命令。Bash 透過特殊識別符號存取這些引數。在 Bash 指令碼中,第一個引數使用 $1 存取,第二個使用 $2,依此類別推。$0 儲存指令碼的名稱,而 $# 傳回引數的總數。
例子:echoparams.sh
#!/bin/bash -
# Cybersecurity Ops with bash
# echoparams.sh
# Description:
# Demonstrates accessing parameters in bash
# Usage:
# ./echoparms.sh <param 1> <param 2> <param 3>
echo $#
echo $0
echo $1
echo $2
echo $3
執行 ./echoparams.sh bash is fun 的輸出如下:
3
./echoparams.sh
bash
is
fun
內容解密:
$#:傳回傳遞給指令碼的引數數量。$0:傳回指令碼的名稱。$1,$2,$3:分別傳回第一、第二和第三個引數。
使用者輸入
Bash 使用 read 命令接收使用者輸入,並將其儲存在指定變數中。下面的指令碼讀取使用者輸入到 MYVAR 變數並列印到螢幕上:
read MYVAR
echo "$MYVAR"
內容解密:
read命令:從標準輸入讀取使用者輸入。- 變數儲存:將輸入儲存在
MYVAR變數中。
條件判斷
Bash 提供豐富的條件判斷陳述式。許多條件陳述式以 if 關鍵字開頭。任何在 Bash 中執行的命令或程式都會傳回成功或失敗的值,這個值可以在命令執行後立即在 $? 變數中找到。
簡單的 if 陳述式形式:
if cmd
then
some cmds
else
other cmds
fi
例如,下面的指令碼嘗試切換目錄到 /tmp。如果該命令成功(傳回 0),則執行 if 陳述式的主體:
if cd /tmp
then
echo "here is what is in /tmp:"
ls -l
fi
Bash 甚至可以處理管道命令的成功或失敗:
if ls | grep pdf
then
echo "found one or more pdf files here"
else
echo "no pdf files found"
fi
內容解密:
if陳述式:根據命令的傳回值(0 為成功,非零為失敗)決定執行路徑。- 管道命令:管道中最後一個命令的傳回值決定了整個管道的成功或失敗。
檔案測試運算元
Bash 可以使用 [[ ]] 或 [ ] 或 test 命令測試檔案屬性或比較值。例如,測試檔案是否存在:
if [[ -e $FILENAME ]]
then
echo $FILENAME exists
fi
常見的檔案測試運算元包括:
-d:測試目錄是否存在。-e:測試檔案是否存在。
內容解密:
[[ -e $FILENAME ]]:檢查$FILENAME是否存在。- 檔案測試運算元:用於檢查檔案或目錄的屬性。
Bash 條件測試與迴圈控制
在 Bash 程式設計中,條件測試與迴圈控制是兩個非常重要的概念。條件測試允許程式根據特定條件執行不同的操作,而迴圈控制則允許程式重複執行某些操作。
檔案測試
Bash 提供了多種檔案測試運算元,用於檢查檔案的屬性,例如是否存在、可讀、可寫或可執行。
-r:測試檔案是否存在且可讀-w:測試檔案是否存在且可寫-x:測試檔案是否存在且可執行
if [[ -r /path/to/file ]]
then
echo "檔案可讀"
fi
內容解密:
這段程式碼檢查 /path/to/file 是否存在且可讀。如果條件成立,則輸出 “檔案可讀”。這裡使用 [[ ]] 進行條件測試,-r 是測試檔案是否可讀的運算元。
數值比較
Bash 支援多種數值比較運算元,用於比較數值的大小或相等性。
-eq:測試兩個數值是否相等-gt:測試一個數值是否大於另一個-lt:測試一個數值是否小於另一個
VAL=10
MIN=5
if [[ $VAL -lt $MIN ]]
then
echo "數值太小"
fi
內容解密:
這段程式碼比較 VAL 和 MIN 的大小。如果 VAL 小於 MIN,則輸出 “數值太小”。這裡使用 [[ ]] 進行條件測試,-lt 是測試數值是否小於的運算元。
使用雙括號進行數值比較
雙括號 (( )) 可以用於數值比較,與 C、Java 或 Python 等語言的語法相似。
VAL=10
if (( VAL < 12 ))
then
echo "數值 $VAL 太小"
fi
內容解密:
這段程式碼檢查 VAL 是否小於 12。如果條件成立,則輸出 “數值 $VAL 太小”。在雙括號內,不需要使用 $ 符號來取得變數的值,除非是位置引數如 $1 和 $2。
邏輯控制
Bash 允許使用 && 和 || 符號進行邏輯控制,根據命令的執行結果決定是否執行後續命令。
[[ -d $DIR ]] && ls "$DIR"
內容解密:
這段程式碼檢查 $DIR 是否為目錄。如果是,則列出目錄中的檔案。這等同於使用 if 陳述式進行相同的檢查。
使用大括號分組命令
當使用 && 或 || 時,如果需要在條件成立或不成立時執行多個命令,可以使用大括號 {} 將這些命令分組。
[[ -d $DIR ]] || { echo "錯誤:目錄不存在:$DIR" ; exit ; }
內容解密:
這段程式碼檢查 $DIR 是否為目錄。如果不是,則輸出錯誤訊息並離開程式。大括號用於將多個命令分組,確保它們在相同的條件下執行。
迴圈控制
Bash 支援多種迴圈控制結構,包括 while 和 for 迴圈。
While 迴圈
while 迴圈會持續執行,直到指定的條件不再成立。
i=0
while (( i < 1000 ))
do
echo $i
let i++
done
內容解密:
這段程式碼使用 while 迴圈輸出從 0 到 999 的數字。迴圈內部使用 let i++ 將 i 的值遞增。
For 迴圈
Bash 中的 for 迴圈有多種形式,包括使用雙括號進行數值迴圈和遍歷引數或任意列表。
for ((i=0; i < 100; i++))
do
echo $i
done
內容解密:
這段程式碼使用 for 迴圈輸出從 0 到 99 的數字。迴圈語法與 C 或 Java 等語言相似,但需要使用雙括號。
###遍歷引數或列表
for VAL in 20 3 dog peach 7 vanilla
do
echo $VAL
done
內容解密:
這段程式碼遍歷指定的列表,並輸出每個元素。列表可以是任意的字串或數字序列。
使用命令生成列表
for VAL in $(ls | grep pdf) {0..5}
do
echo $VAL
done
內容解密:
這段程式碼首先使用 ls 和 grep 命令找出包含 “pdf” 的檔案名稱,然後遍歷這些名稱和數字序列 {0..5},輸出每個元素。這裡展示瞭如何將命令的輸出作為迴圈的輸入。
Bash 函式與模式匹配詳解
Bash 函式的定義與使用
Bash 函式是一種將多個命令組織在一起以執行特定任務的程式碼塊。定義一個函式的基本語法如下:
function myfun() {
# 函式主體
}
在這個語法中,function 關鍵字是可選的,但使用它可以提高程式碼的可讀性。函式定義後,可以像執行普通命令一樣呼叫它,並傳遞引數。
函式引數處理
在函式內部,引數透過 $1、$2 等變數來存取,這些變數會遮蔽指令碼原本的引數。因此,如果需要在函式記憶體取指令碼的原始引數,應在呼叫函式前將它們儲存在其他變數中。
#!/bin/bash
# 定義函式
function myfun() {
echo "函式內引數數量: $#"
echo "第一個引數: $1"
}
# 呼叫函式並傳遞引數
myfun "Hello" "World"
#### 內容解密:
1. `$#` 用於取得傳遞給函式的引數數量。
2. `$1` 用於存取第一個引數。
3. 函式內的變數預設為全域變數,除非使用 `local` 關鍵字宣告為區域性變數。
傳回值與輸出處理
函式可以透過傳回狀態碼或輸出結果來傳遞資訊。傳回狀態碼通常用於表示函式執行是否成功,而輸出結果可以用於取得計算值或路徑名等。
#!/bin/bash
# 定義函式
function compute() {
local result=$(( $1 + $2 ))
echo "$result"
}
# 呼叫函式並捕捉輸出
RETVAL=$(compute 10 20)
echo "計算結果: $RETVAL"
#### 內容解密:
1. `local` 關鍵字用於宣告區域性變數,避免影響全域變數。
2. `echo` 用於輸出計算結果。
3. `$( )` 用於捕捉命令的輸出結果。
Bash 中的模式匹配
Bash 提供了模式匹配功能,允許使用者透過模式指定一組檔案。最簡單的模式匹配符號是 *,它可以匹配任意數量和型別的字元。
基本模式匹配符號
*:匹配任意字元序列。?:匹配單個字元。[ ]:匹配方括號內列出的任意一個字元。
# 列出當前目錄下所有 .txt 檔案
ls *.txt
# 列出 /usr/bin 目錄下以 g 開頭的檔案
ls /usr/bin/g*
#### 內容解密:
1. `*` 用於匹配任意字元序列,例如 `*.txt` 匹配所有以 `.txt` 結尾的檔案。
2. `?` 用於匹配單個字元,例如 `source.?` 可能匹配 `source.c` 或 `source.o`。
3. `[ ]` 用於匹配特定字元集合,例如 `x[abc]y` 匹配 `xay`、`xby` 或 `xcy`。
字元類別
Bash 支援使用字元類別進行模式匹配,這需要在方括號內使用特定的類別名稱。
# 列出包含標點符號並以 .jpg 結尾的檔案
ls *[[:punct:]]*.jpg
#### 內容解密:
1. `[[:punct:]]` 用於匹配標點符號。
2. 字元類別需要放在方括號內,例如 `[[:punct:]]`。
3. 這種模式將匹配包含標點符號且以 `.jpg` 結尾的檔案名。