前言
Shell 腳本編程是 Linux 和 Unix 系統管理、自動化任務的核心工具之一。通過 Shell 腳本,你可以自動化重復性操作、簡化復雜流程、提高系統管理效率,甚至構建完整的自動化運維工具。本文將帶你從基礎到進階,全面學習 Shell 腳本編程,涵蓋語法、結構、調試、最佳實踐等內容。
一、Shell 簡介與環境搭建
1.1 什么是 Shell?
Shell 是命令行解釋器,是用戶與操作系統內核之間的橋梁。它接收用戶輸入的命令,并調用相應的程序或服務來執行。
常見的 Shell 有:
- Bash(Bourne-Again Shell):Linux 系統默認的 Shell,廣泛使用。
- sh(Bourne Shell):早期的 Unix Shell,兼容性好。
- zsh(Z Shell):功能更強大、交互性更強的現代 Shell。
- PowerShell:Windows 和跨平臺環境下使用的 Shell,語法不同但功能類似。
本文以 Bash 為例進行講解。
1.2 環境準備
在 Linux 或 macOS 系統中,Bash 通常已經預裝。可以通過以下命令查看當前 Shell:
echo $SHELL
如果你使用的是 Windows,可以安裝:
- Windows Subsystem for Linux (WSL):推薦使用 WSL2,支持完整的 Linux 環境。
- Git Bash:輕量級的 Bash 環境,適合開發人員。
二、第一個 Shell 腳本
2.1 編寫腳本
創建一個名為 hello.sh
的文件:
#!/bin/bash
# 這是我的第一個 Shell 腳本echo "Hello, World!"
2.2 執行腳本
給腳本添加執行權限:
chmod +x hello.sh
執行腳本:
./hello.sh
2.3 解釋 shebang
#!/bin/bash
被稱為 shebang,用于告訴系統該腳本應使用哪個解釋器來執行。不同的 shebang 可以指定不同的 Shell,例如:
#!/bin/sh
:使用 Bourne Shell#!/usr/bin/env python
:使用 Python 解釋器運行腳本
三、Shell 腳本基礎語法
3.1 變量定義與使用
Shell 中變量不需要聲明類型,賦值時等號兩側不能有空格:
name="Qwen"
echo "Hello, $name"
可以使用 ${name}
來避免歧義:
echo "Hello, ${name}_user"
3.2 命令替換
將命令的輸出結果賦值給變量:
current_date=$(date)
echo "當前時間是:$current_date"
也可以使用反引號實現相同功能:
current_date=`date`
3.3 輸入輸出操作
讀取用戶輸入:
read -p "請輸入你的名字:" username
echo "你好,$username"
輸出重定向:
echo "Hello" > output.txt # 覆蓋寫入
echo "World" >> output.txt # 追加寫入
四、條件判斷與控制結構
4.1 if 語句
age=20
if [ $age -ge 18 ]; thenecho "你已成年"
elseecho "你還未成年"
fi
4.2 比較運算符
運算符 | 含義 |
---|---|
-eq | 等于 |
-ne | 不等于 |
-lt | 小于 |
-le | 小于等于 |
-gt | 大于 |
-ge | 大于等于 |
4.3 字符串比較
str1="hello"
str2="world"
if [ "$str1" = "$str2" ]; thenecho "字符串相等"
elseecho "字符串不相等"
fi
4.4 case 語句
case $1 instart)echo "啟動服務";;stop)echo "停止服務";;*)echo "未知命令";;
esac
五、循環結構
5.1 for 循環
for i in {1..5}; doecho "第 $i 次循環"
done
遍歷數組:
names=("Alice" "Bob" "Charlie")
for name in "${names[@]}"; doecho "Hello, $name"
done
5.2 while 循環
count=1
while [ $count -le 5 ]; doecho "計數:$count"count=$((count + 1))
done
5.3 until 循環(直到條件為真)
count=1
until [ $count -gt 5 ]; doecho "計數:$count"count=$((count + 1))
done
六、函數與模塊化編程
6.1 定義函數
greet() {echo "你好,$1"
}greet "Alice"
greet "Bob"
6.2 返回值與局部變量
add() {local result=$(( $1 + $2 ))echo $result
}sum=$(add 3 5)
echo "3 + 5 = $sum"
6.3 函數參數傳遞
函數參數通過 $1
, $2
, ... 傳遞:
log() {echo "[$(date +%H:%M:%S)] $1"
}log "腳本開始執行"
七、數組與集合操作
7.1 定義數組
fruits=("apple" "banana" "cherry")
7.2 訪問數組元素
echo "第一個水果是:${fruits[0]}"
echo "所有水果是:${fruits[@]}"
7.3 遍歷數組
for fruit in "${fruits[@]}"; doecho "$fruit"
done
7.4 數組操作
- 添加元素:
fruits+=("orange")
- 刪除元素:
unset fruits[1]
- 獲取數組長度:
echo "水果數量:${#fruits[@]}"
八、腳本參數與退出狀態
8.1 獲取腳本參數
echo "腳本名稱:$0"
echo "第一個參數:$1"
echo "所有參數:$@"
echo "參數個數:$#"
8.2 退出狀態碼
腳本的退出狀態碼用于表示執行是否成功:
exit 0 # 成功
exit 1 # 錯誤
可以通過 $?
獲取上一個命令的退出狀態:
ls /nonexistent
echo "上一個命令的狀態碼:$?"
九、文件與目錄操作
9.1 文件測試
if [ -f "file.txt" ]; thenecho "文件存在"
fi
常見測試操作符:
操作符 | 含義 |
---|---|
-f | 是否為文件 |
-d | 是否為目錄 |
-r | 是否可讀 |
-w | 是否可寫 |
-x | 是否可執行 |
9.2 文件操作示例
- 創建文件:
touch newfile.txt
- 刪除文件:
rm -f file.txt
- 移動/重命名文件:
mv oldname.txt newname.txt
- 查看文件內容:
cat file.txt
十、調試與優化腳本
10.1 調試腳本
使用 -x
參數調試腳本:
bash -x script.sh
或者在腳本開頭加上:
set -x
關閉調試:
set +x
10.2 腳本優化技巧
- 使用?
set -u
?防止使用未定義變量。 - 使用?
set -e
?在出現錯誤時立即退出腳本。 - 使用?
trap
?捕獲信號,進行清理操作:
trap "echo '腳本被中斷'; exit 1" INT
- 使用?
getopts
?解析命令行參數:
while getopts "a:b:c" opt; docase $opt ina)echo "選項 a 的值:$OPTARG";;b)echo "選項 b 的值:$OPTARG";;c)echo "選項 c 被設置";;\?)echo "無效選項:-$OPTARG";;esac
done
十一、實戰項目:自動化備份腳本
#!/bin/bash# 設置變量
backup_dir="/backup"
source_dir="/home/user/documents"
timestamp=$(date +%Y%m%d%H%M%S)
backup_file="$backup_dir/backup_$timestamp.tar.gz"# 創建備份目錄(如果不存在)
mkdir -p $backup_dir# 執行備份
tar -czf $backup_file $source_dir# 檢查是否成功
if [ $? -eq 0 ]; thenecho "備份完成:$backup_file"
elseecho "備份失敗"exit 1
fi
十二、高級主題與最佳實踐
12.1 使用正則表達式
if [[ "hello123" =~ ^[a-zA-Z0-9]+$ ]]; thenecho "匹配成功"
fi
12.2 使用關聯數組(Bash 4+)
declare -A user_info
user_info["name"]="Alice"
user_info["age"]=25
echo "用戶姓名:${user_info[name]}"
12.3 使用子 Shell
(cd /tmptouch testfile
)
12.4 使用 Here Document
cat << EOF > output.txt
這是第一行
這是第二行
EOF
12.5 使用別名與函數庫
可以將常用函數放入一個 .sh
文件中作為庫文件:
# utils.sh
log() {echo "[$(date +%H:%M:%S)] $1"
}
在主腳本中引用:
source utils.sh
log "腳本開始執行"