Shell脚本基础(一)

Shell 脚本编程是 Linux 和 Unix 系统管理、自动化任务的核心工具之一。通过 Shell 脚本,你可以自动化重复性操作、简化复杂流程、提高系统管理效率,甚至构建完整的自动化运维工具。本文将带你从基础到进阶,全面学习 Shell 脚本编程,涵盖语法、结构、调试、最佳实践等内容。 一、Sh

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(或hashbang、pound bang等),用于告诉系统该脚本应使用哪个解释器来执行。不同的 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 ]; then
    echo "你已成年"
else
    echo "你还未成年"
fi

4.2 比较运算符

运算符 含义
-eq 等于
-ne 不等于
-lt 小于
-le 小于等于
-gt 大于
-ge 大于等于

4.3 字符串比较

str1="hello"
str2="world"
if [ "$str1" = "$str2" ]; then
    echo "字符串相等"
else
    echo "字符串不相等"
fi

4.4 case 语句

case $1 in
    start)
        echo "启动服务"
        ;;
    stop)
        echo "停止服务"
        ;;
    *)
        echo "未知命令"
        ;;
esac

五、循环结构

5.1 for 循环

for i in {1..5}; do
    echo "第 $i 次循环"
done

遍历数组:

names=("Alice" "Bob" "Charlie")
for name in "${names[@]}"; do
    echo "Hello, $name"
done

5.2 while 循环

count=1
while [ $count -le 5 ]; do
    echo "计数:$count"
    count=$((count + 1))
done

5.3 until 循环(直到条件为真)

count=1
until [ $count -gt 5 ]; do
    echo "计数:$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[@]}"; do
    echo "$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" ]; then
    echo "文件存在"
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; do
        case $opt in
            a)
                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 ]; then
    echo "备份完成:$backup_file"
else
    echo "备份失败"
    exit 1
fi

十二、高级主题与最佳实践

12.1 使用正则表达式

if [[ "hello123" =~ ^[a-zA-Z0-9]+$ ]]; then
    echo "匹配成功"
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 /tmp
    touch 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 "脚本开始执行"

Comment