飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 基础入门

    • Linux 命令概述与环境准备
    • 文件与目录操作(上):ls、cd、pwd
    • 文件与目录操作(下):cat、touch、mkdir、rm、cp、mv
    • 获取帮助与基础工具:man、help、clear、exit、sudo
  • 文件权限

    • 文件权限管理:chmod、chown
  • 系统与监控

    • 进程管理与系统监控:ps、top、kill、df、du、free
    • 用户与环境:whoami、id、env、echo、history
  • 网络命令

    • 网络诊断(上):ping、curl、wget
    • 网络诊断(下):netstat、ss、ip
    • 网络抓包与高级工具:traceroute、tcpdump、nc
    • DNS 与防火墙:nslookup、dig、iptables
  • 文本处理

    • 文本搜索:grep
    • 文本处理(上):awk
    • 文本处理(下):sed
    • 文本排序与统计:sort、uniq、wc
    • 文件查看与查找:head、tail、cut、find
    • 归档压缩与管道组合:tar、gzip、xargs
  • 开发工具

    • 开发工具链(上):gcc、make
    • 开发工具链(下):gdb、time
    • 版本控制:git
    • 远程操作:ssh、scp
    • 编辑器:vim、nano
  • Shell 编程

    • Shell 编程基础(上):变量、管道、重定向
    • Shell 编程基础(下):条件、循环、函数
  • 综合实战

    • 综合实战:日志分析

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 file2file1 比 file2 新[ file1 -nt file2 ]
file1 -ot file2file1 比 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 定义局部变量,不影响外部

动手实践

  1. 条件判断练习:

    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
    
  2. 循环练习:

    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
    
  3. 函数练习:

    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
    
  4. 文件处理练习:

    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
    
  5. 备份脚本练习:

    # 创建上面的 backup.sh 脚本
    # 修改 SOURCE_DIR 为 ~/test_backup
    # 创建测试目录和文件
    mkdir -p ~/test_backup
    touch ~/test_backup/file{1..5}.txt
    
    # 运行备份脚本
    ./backup.sh
    
    # 查看备份结果
    ls -lh ~/Backups/
    
  6. 思考:Shell 函数的 return 只能返回 0-255 的整数,如果需要返回更大的数字或字符串,应该用什么方法?为什么 local 变量在函数中很重要?如果不使用 local,会有什么副作用?

上一页
Shell 编程基础(上):变量、管道、重定向