Shell 编程基础(下):条件、循环、函数
条件判断
test 命令和 [ ]
#!/bin/bash
# test 命令
if test -f file.txt; then
echo "File exists"
fi
# [ ] 是 test 的简写(注意:括号内必须有空格!)
if [ -f file.txt ]; then
echo "File exists"
fi
# [[ ]] 是 [ ] 的增强版(推荐,支持正则)
if [[ -f file.txt ]]; then
echo "File exists"
fi
文件测试
| 测试 | 含义 | 示例 |
|---|---|---|
-e file | 文件存在 | [ -e file.txt ] |
-f file | 是普通文件 | [ -f file.txt ] |
-d file | 是目录 | [ -d /tmp ] |
-r file | 可读 | [ -r file.txt ] |
-w file | 可写 | [ -w file.txt ] |
-x file | 可执行 | [ -x script.sh ] |
-s file | 非空(大小 > 0) | [ -s file.txt ] |
-L file | 是符号链接 | [ -L link.txt ] |
file1 -nt file2 | file1 比 file2 新 | [ file1 -nt file2 ] |
file1 -ot file2 | file1 比 file2 旧 | [ file1 -ot file2 ] |
字符串测试
| 测试 | 含义 | 示例 |
|---|---|---|
-z str | 字符串长度为 0 | [ -z "$var" ] |
-n str | 字符串长度非 0 | [ -n "$var" ] |
str1 = str2 | 相等 | [ "$a" = "$b" ] |
str1 != str2 | 不相等 | [ "$a" != "$b" ] |
str1 < str2 | 字典序小于 | [[ "$a" < "$b" ]] |
str1 > str2 | 字典序大于 | [[ "$a" > "$b" ]] |
数值测试
| 测试 | 含义 | 示例 |
|---|---|---|
-eq | 等于(equal) | [ $a -eq $b ] |
-ne | 不等于(not equal) | [ $a -ne $b ] |
-gt | 大于(greater than) | [ $a -gt $b ] |
-ge | 大于等于 | [ $a -ge $b ] |
-lt | 小于(less than) | [ $a -lt $b ] |
-le | 小于等于 | [ $a -le $b ] |
注意:Shell 中数值比较用 -eq 等,不能用 = 或 ==(那是字符串比较)。
if 语句
#!/bin/bash
# 基本 if
if [ -f file.txt ]; then
echo "File exists"
fi
# if-else
if [ -f file.txt ]; then
echo "File exists"
else
echo "File does not exist"
fi
# if-elif-else
if [ $score -ge 90 ]; then
echo "A"
elif [ $score -ge 80 ]; then
echo "B"
elif [ $score -ge 70 ]; then
echo "C"
elif [ $score -ge 60 ]; then
echo "D"
else
echo "F"
fi
# 多条件组合(-a = and, -o = or)
if [ -f file.txt -a -r file.txt ]; then
echo "File exists and is readable"
fi
# [[ ]] 支持 && 和 ||
if [[ -f file.txt && -r file.txt ]]; then
echo "File exists and is readable"
fi
# 命令返回值判断
if grep -q "error" log.txt; then
echo "Found error in log"
fi
case 语句
#!/bin/bash
case $1 in
start)
echo "Starting service..."
;;
stop)
echo "Stopping service..."
;;
restart)
echo "Restarting service..."
;;
status)
echo "Checking status..."
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
循环
for 循环
#!/bin/bash
# 遍历列表
for i in 1 2 3 4 5; do
echo "Number: $i"
done
# 遍历范围(Bash 4.0+)
for i in {1..5}; do
echo "Number: $i"
done
# 带步长
for i in {1..10..2}; do
echo "Number: $i" # 1, 3, 5, 7, 9
done
# C 风格
for ((i=0; i<5; i++)); do
echo "Index: $i"
done
# 遍历文件
for file in *.txt; do
echo "Processing: $file"
done
# 遍历命令输出
for user in $(cat /etc/passwd | awk -F: '{print $1}'); do
echo "User: $user"
done
# 遍历数组
fruits=("apple" "banana" "cherry")
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done
while 循环
#!/bin/bash
# 基本 while
i=1
while [ $i -le 5 ]; do
echo "Count: $i"
i=$((i + 1))
done
# 读取文件(逐行)
while read line; do
echo "Line: $line"
done < file.txt
# 读取文件(带行号)
line_num=0
while read line; do
line_num=$((line_num + 1))
echo "$line_num: $line"
done < file.txt
# 无限循环(配合 break)
while true; do
read -p "Enter command (q to quit): " cmd
if [ "$cmd" = "q" ]; then
break
fi
echo "Executing: $cmd"
done
# 读取用户输入直到空行
while read -p "Enter line (empty to finish): " line && [ -n "$line" ]; do
echo "You entered: $line"
done
until 循环
#!/bin/bash
# until 条件为真时停止(与 while 相反)
i=1
until [ $i -gt 5 ]; do
echo "Count: $i"
i=$((i + 1))
done
函数
定义和调用
#!/bin/bash
# 定义函数
hello() {
echo "Hello, World!"
}
# 带参数的函数
greet() {
echo "Hello, $1!"
echo "You are $2 years old."
}
# 带返回值的函数(只能返回 0-255 的整数)
add() {
return $(($1 + $2))
}
# 使用 echo 返回字符串结果
get_date() {
echo "$(date +%Y-%m-%d)"
}
# 调用函数
hello
greet "Alice" 25
add 3 5
result=$? # 获取返回值($? 是上一个命令的退出状态)
echo "3 + 5 = $result"
# 获取 echo 返回的结果
today=$(get_date)
echo "Today is $today"
函数高级用法
#!/bin/bash
# 局部变量(local)
counter() {
local count=0 # 局部变量,不影响外部
count=$((count + 1))
echo "Local count: $count"
}
count=100
counter
echo "Global count: $count" # 仍然是 100
# 检查参数个数
process_file() {
if [ $# -ne 1 ]; then
echo "Usage: process_file <filename>"
return 1
fi
if [ ! -f "$1" ]; then
echo "Error: File '$1' not found"
return 2
fi
echo "Processing: $1"
wc -l "$1"
}
process_file file.txt
综合实战:备份脚本
#!/bin/bash
# backup.sh - 自动备份脚本
# 配置
SOURCE_DIR="$HOME/Documents"
BACKUP_DIR="$HOME/Backups"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_${DATE}.tar.gz"
RETENTION_DAYS=7
# 检查源目录
if [ ! -d "$SOURCE_DIR" ]; then
echo "Error: Source directory '$SOURCE_DIR' not found"
exit 1
fi
# 创建备份目录
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
echo "Created backup directory: $BACKUP_DIR"
fi
# 执行备份
echo "Starting backup..."
echo "Source: $SOURCE_DIR"
echo "Destination: $BACKUP_DIR/$BACKUP_FILE"
if tar -czf "$BACKUP_DIR/$BACKUP_FILE" -C "$SOURCE_DIR" .; then
echo "Backup completed: $BACKUP_FILE"
ls -lh "$BACKUP_DIR/$BACKUP_FILE"
else
echo "Backup failed!"
exit 1
fi
# 清理旧备份
echo "Cleaning up backups older than $RETENTION_DAYS days..."
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +$RETENTION_DAYS -delete
echo "Cleanup completed"
echo "All done!"
本篇小结
- 条件判断:
[ ]和[[ ]],[[ ]]支持&&||和正则,推荐 - 文件测试:
-e存在,-f普通文件,-d目录,-r可读,-w可写,-x可执行 - 字符串测试:
-z空,-n非空,=相等,!=不相等 - 数值测试:
-eq等于,-ne不等于,-gt大于,-lt小于 if-elif-else-fi,case-esac多分支for循环:遍历列表/范围/C 风格;while循环:条件为真执行;until循环:条件为真停止break跳出循环,continue跳过本次- 函数:
func() { ... },$1-$n访问参数,return返回 0-255,echo+$(func)返回字符串 local定义局部变量,不影响外部
动手实践
条件判断练习:
cat > check.sh #!/bin/bash if [ -f /etc/passwd ]; then echo "passwd exists" fi if [ $# -gt 0 ]; then echo "Arguments: $@" else echo "No arguments" fi # Ctrl+D chmod +x check.sh ./check.sh ./check.sh arg1 arg2循环练习:
cat > loop.sh #!/bin/bash for i in {1..5}; do echo "Number: $i" done i=1 while [ $i -le 5 ]; do echo "While: $i" i=$((i + 1)) done # Ctrl+D chmod +x loop.sh ./loop.sh函数练习:
cat > func.sh #!/bin/bash add() { echo $(($1 + $2)) } result=$(add 10 20) echo "10 + 20 = $result" # Ctrl+D chmod +x func.sh ./func.sh文件处理练习:
cat > process.sh #!/bin/bash for file in *.txt; do if [ -f "$file" ]; then lines=$(wc -l < "$file") echo "$file: $lines lines" fi done # Ctrl+D chmod +x process.sh touch a.txt b.txt c.txt ./process.sh备份脚本练习:
# 创建上面的 backup.sh 脚本 # 修改 SOURCE_DIR 为 ~/test_backup # 创建测试目录和文件 mkdir -p ~/test_backup touch ~/test_backup/file{1..5}.txt # 运行备份脚本 ./backup.sh # 查看备份结果 ls -lh ~/Backups/思考:Shell 函数的
return只能返回 0-255 的整数,如果需要返回更大的数字或字符串,应该用什么方法?为什么local变量在函数中很重要?如果不使用local,会有什么副作用?