【Linux】shell 脚本-系统管理与任务自动化(语法使用介绍)

Shell 脚本是用于在 Unix/Linux 命令行解释器(Shell)中自动化执行一系列命令的文本文件。掌握其基础语法对于系统管理、任务自动化和开发运维至关重要。

一、脚本基础结构

  1. Shebang 行 (必需):

    #!/bin/bash
    
    • 必须是脚本的第一行
    • #! 告诉系统使用哪个解释器执行脚本。
    • /bin/bash 指定使用 Bash Shell(最常见)。也可以是 /bin/sh, /bin/zsh, /usr/bin/python3 等。
    • 系统根据这一行找到正确的解释器来运行脚本。
  2. 注释:

    # 这是一个单行注释
    
    : '
    这是一个
    多行注释
    (Bash 风格)
    '
    
    <<COMMENT
    这也是一个
    多行注释
    (Heredoc 风格)
    COMMENT
    
    • # 开头的行是单行注释(除了 Shebang)。
    • 多行注释通常使用 : ' ... ' 或 Here Document < 技巧实现。

二、变量

  1. 定义与赋值:

    variable_name=value    # 等号两边不能有空格!
    name="John Doe"        # 包含空格的值必须用引号括起来
    count=42               # 数字不需要引号
    files=$(ls)            # 命令替换:将命令输出赋值给变量
    files=`ls`             # 旧式命令替换(反引号),建议用 $()
    today=$(date +%F)      # 将格式化后的日期赋值给变量
    
    • 变量名区分大小写,通常大写用于环境变量,小写用于脚本变量(约定)。
    • 赋值时等号 = 两边不能有空格
    • 值包含空格、特殊字符时,必须用引号(单引号或双引号)。
  2. 使用变量:

    echo $name         # 使用变量:$变量名
    echo "Hello, $name" # 双引号内变量会被替换(扩展)
    echo 'Hello, $name' # 单引号内变量不会被替换,原样输出
    echo "Count: ${count}" # 使用 {} 明确变量边界,推荐总是使用
    echo "Files: ${files[@]}" # 访问数组所有元素
    echo "Arg 1: $1"    # 位置参数:$1, $2, ... $9, ${10}, ...
    
    • $variable_name${variable_name} 获取变量值。
    • 双引号 ":允许变量扩展、命令替换和转义字符。
    • 单引号 ':所有内容原样输出,不进行任何替换或转义。
    • 反引号 `$():用于命令替换,执行命令并将其输出作为值。
  3. 位置参数:

    • $0:脚本名称本身。
    • $1, $2, $3, …:传递给脚本的第一个、第二个、第三个参数。
    • $#:传递给脚本的参数个数。
    • $@:所有位置参数的列表(每个参数作为独立的字符串)。
    • $*:所有位置参数合并成一个字符串。
    • shift:将位置参数向左移动($1 丢弃,$2 变成 $1,依此类推),常用于循环处理参数。
  4. 特殊变量:

    • $?:上一个命令的退出状态码(0 表示成功,非 0 表示失败)。
    • $$:当前 Shell 进程的进程 ID (PID)。
    • $!:上一个在后台运行的命令的 PID。
    • $-:当前 Shell 的选项标志。
  5. 数组:

    fruits=("Apple" "Banana" "Orange")  # 定义数组
    echo ${fruits[0]}                  # 访问第一个元素:Apple
    echo ${fruits[@]}                  # 访问所有元素
    echo ${#fruits[@]}                 # 数组元素个数:3
    fruits[3]="Grape"                  # 添加元素
    unset fruits[1]                    # 删除第二个元素(Banana)
    

三、输入与输出

  1. 输出 (echo, printf):

    echo "Hello World"                # 基本输出
    echo -e "Line 1\nLine 2"           # -e 启用转义字符(如 \n 换行)
    printf "Name: %-10s Age: %d\n" "$name" $age # 格式化输出,类似 C 语言
    
  2. 输入 (read):

    read -p "Enter your name: " username # -p 显示提示符
    read -s -p "Password: " password    # -s 静默输入(不回显)
    echo "Hello, $username!"            # 使用读取的值
    read first second                  # 读取多个值到不同变量
    read -a array                      # 读取一行输入到数组 array
    

四、条件判断 (if, case, test/[ ]/[[ ]])

  1. test 命令与方括号 [ ]

    if [ "$name" = "Alice" ]; then     # 字符串相等比较。注意空格和引号!
        echo "Hi Alice!"
    elif [ $age -gt 18 ]; then         # 数字比较:-gt (大于), -lt (小于), -eq (等于), -ne (不等于), -ge (大于等于), -le (小于等于)
        echo "You are an adult."
    elif [ -f "/path/to/file" ]; then  # 文件测试:-f (是普通文件), -d (是目录), -e (存在), -r (可读), -w (可写), -x (可执行)
        echo "File exists."
    else
        echo "Who are you?"
    fi
    
    • [test 命令的别名,] 是必须的结尾符
    • 表达式和方括号 [ ] 之间必须有空格
    • 字符串比较用 =!=,数字比较用 -eq, -ne 等操作符。
    • 强烈建议总是用双引号包裹变量(如 "$name"),防止变量为空或包含空格导致语法错误。
  2. 双括号 [[ ]] (Bash 扩展,更推荐):

    if [[ $name == "Alice" && $age -ge 18 ]]; then # 逻辑与 (&&), 或 (||)
        echo "Welcome Alice!"
    elif [[ -f "$file" && -s "$file" ]]; then      # 文件存在且非空
        echo "File is valid."
    elif [[ "$str" =~ ^[A-Za-z]+$ ]]; then         # 正则表达式匹配 (=~)
        echo "String is alphabetic."
    fi
    
    • [ ] 更强大、更安全(例如在字符串比较中不需要额外引号防止空变量)。
    • 支持模式匹配 (==!= 右侧可用通配符 *, ?, [])。
    • 支持正则表达式匹配 (=~)。
    • 逻辑操作符 &&|| 可以直接在 [[ ... ]] 内使用。
  3. case 语句 (多路分支):

    case $fruit in
        "Apple")
            echo "It's an apple."
            ;;
        "Banana" | "Orange")  # 匹配多个模式
            echo "It's a banana or orange."
            ;;
        *)                   # 默认匹配所有其他情况
            echo "Unknown fruit."
            ;;
    esac
    
    • 每个模式以 ) 结束。
    • 每个分支以 ;; 结束。
    • | 用于分隔多个模式。
    • * 是通配符,匹配任何值。

五、循环 (for, while, until)

  1. for 循环:

    # 遍历列表
    for color in red green blue; do
        echo "Color: $color"
    done
    
    # 遍历命令输出结果(按空格/换行符分隔)
    for file in $(ls *.txt); do
        echo "Processing: $file"
    done
    
    # C 语言风格 (Bash)
    for ((i=0; i<10; i++)); do
        echo "Number: $i"
    done
    
    # 遍历数组元素
    for item in "${fruits[@]}"; do
        echo "Fruit: $item"
    done
    
  2. while 循环 (当条件为真时循环):

    count=1
    while [ $count -le 5 ]; do
        echo "Count: $count"
        ((count++))             # 算术运算:(()) 或 let count++
    done
    
    # 逐行读取文件
    while IFS= read -r line; do # IFS= 防止前导/尾随空格被修剪,-r 防止反斜杠转义
        echo "Line: $line"
    done < "input.txt"          # 输入重定向
    
  3. until 循环 (当条件为假时循环):

    count=10
    until [ $count -eq 0 ]; do  # 当 $count 等于 0 时停止
        echo "Countdown: $count"
        ((count--))
    done
    

六、函数

  1. 定义与调用:

    # 定义函数
    greet() {
        local greeting="Hello"   # local 定义局部变量(仅在函数内有效)
        echo "$greeting, $1!"
    }
    
    # 调用函数
    greet "World"               # 传递参数 "World"(在函数内通过 $1 访问)
    result=$(greet "Alice")     # 捕获函数输出
    
    • 函数名后必须有 ()
    • 函数体在 { } 内。
    • 参数通过位置参数 $1, $2, … 在函数内部访问。
    • 使用 local 关键字定义局部变量,避免污染全局作用域。
    • 使用 return N 返回一个退出状态码(0 表示成功,1-255 表示失败)。要返回数据,请使用 echo 输出并在调用处用 $(...) 捕获

七、命令执行与流程控制

  1. 退出脚本 (exit):

    if [ ! -f "required.txt" ]; then
        echo "Error: File not found!" >&2 # 输出到标准错误
        exit 1                           # 非 0 退出码表示错误
    fi
    exit 0                               # 成功退出
    
  2. 命令组合:

    • (command1; command2): 在子Shell中执行命令序列。
    • { command1; command2; }: 在当前Shell中执行命令序列(注意 { 后必须有空格,最后一条命令后必须有 ; 或换行,} 前必须有 ; 或换行)。
  3. 逻辑操作符 (&&, ||):

    command1 && command2  # 仅当 command1 成功(退出码 0)才执行 command2
    command1 || command2  # 仅当 command1 失败(退出码非 0)才执行 command2
    

八、输入/输出重定向

  • command > file:将 command标准输出重定向到 file(覆盖)。
  • command >> file:将 command标准输出重定向到 file(追加)。
  • command < file:将 file 的内容作为 command标准输入
  • command 2> file:将 command标准错误重定向到 file(覆盖)。
  • command 2>> file:将 command标准错误重定向到 file(追加)。
  • command > file 2>&1command &> file:将标准输出和标准错误都重定向到 file(覆盖)。
  • command >> file 2>&1command &>> file:将标准输出和标准错误都重定向到 file(追加)。
  • command | command2:将 command标准输出通过管道传递给 command2标准输入

九、其他重要概念

  1. 引号:

    • 单引号 ': 强引用,内部所有字符(包括 $\)都失去特殊意义,原样输出。
    • 双引号 ": 弱引用,允许变量扩展 ($var)、命令替换 ($(...)`...`) 和转义字符 (\n, \t, \", \\)。是最常用且安全的选择(尤其当变量值可能包含空格时)。
    • 反引号 `: 用于命令替换(同 $(),但 $() 更清晰且支持嵌套)。
  2. 算术运算:

    a=5
    b=3
    sum=$((a + b))       # 推荐:(( ... )) 表达式计算
    ((product = a * b))  # 计算结果可赋值给变量(变量前无 $)
    let "diff = a - b"   # let 命令
    increment=$((a++))   # 后置自增
    decrement=$((--b))   # 前置自减
    
  3. 调试:

    • bash -x script.sh: 在执行时显示每条命令及其展开后的参数(极其有用)。
    • bash -n script.sh: 只检查脚本语法错误,不执行。
    • 在脚本中临时启用调试:set -x (开始) / set +x (结束)。

十、示例脚本

#!/bin/bash
# 计算 1 到 N 的平方和

# 获取用户输入
read -p "Enter a number (N): " n

# 检查输入是否是数字
if ! [[ "$n" =~ ^[0-9]+$ ]]; then
    echo "Error: Please enter a positive integer." >&2
    exit 1
fi

# 初始化变量
sum=0
counter=1

# 使用 while 循环计算平方和
while [ $counter -le $n ]; do
    square=$((counter * counter))
    sum=$((sum + square))
    ((counter++))
done

# 输出结果
echo "The sum of squares from 1 to $n is: $sum"
exit 0

十一、最佳实践与安全注意事项

  1. 始终使用 #!/bin/bash (或明确指定所需 Shell)。
  2. 总是用双引号包裹变量引用 ("$var"),除非你明确知道不需要(非常罕见)。
  3. 对文件路径使用引号 (cp "$source" "$dest")。
  4. 使用 [[ ]] 代替 [ ] (Bash 中),功能更强更安全。
  5. 检查命令执行结果 (if command; then ...command || handle_error)。
  6. 检查文件/目录是否存在权限 再进行操作。
  7. 使用 set -euo pipefail 或类似选项 (在脚本开头) 增强健壮性:
    • set -e: 命令失败(非 0 退出码)立即退出脚本。
    • set -u: 遇到未定义变量时报错并退出。
    • set -o pipefail: 管道中任何一个命令失败,整个管道视为失败。
  8. 谨慎使用 rm -rf,尤其当路径包含变量时。可以先 echo 要删除的内容检查。
  9. 对用户输入进行验证
  10. 编写清晰的注释
  11. 使用 local 声明函数内的局部变量
  12. 使用 $(...) 代替反引号 `...` 进行命令替换
  13. 使用 read -r 读取输入行,防止反斜杠转义。

Shell 脚本来解决各种自动化任务,不同的 Shell (如 sh, bash, zsh, ksh) 可能有细微差别,Bash 是最通用的选择。

你可能感兴趣的:(Linux,linux,bash)