写给同事的Shell入门教程

0 阅读6分钟

Shell脚本入门指南

从入门到实战:30分钟掌握Linux自动化脚本编写

目录

  1. 什么是Shell脚本
  2. 为什么学习Shell脚本
  3. 环境准备
  4. 第一个Shell脚本
  5. 变量与数据类型
  6. 条件判断
  7. 循环结构
  8. 函数
  9. 输入输出重定向
  10. 调试技巧
  11. 实用示例
  12. 最佳实践

什么是Shell脚本

Shell脚本是一个包含一系列命令的文本文件,由Shell解释器逐行执行。它允许你将多个命令组合在一起,通过运行一个脚本来自动完成复杂的任务。

Shell与Bash的区别

  • Shell: 提供命令行界面与操作系统交互的程序的统称
  • Bash (Bourne-Again SHell): 最常用的Unix/Linux Shell之一,是许多Linux发行版的默认Shell

其他常见的Shell还包括:

  • Korn Shell (ksh)
  • C Shell (csh)
  • Z Shell (zsh)

为什么学习Shell脚本

1. 自动化重复任务

将手动执行的命令序列化为脚本,一键完成复杂操作。

2. 系统管理

批量管理文件、监控系统状态、定时执行任务。

3. 提高开发效率

快速搭建开发环境、自动化部署、日志分析。

4. 跨平台兼容

可在Unix、Linux、macOS上运行,Windows也可通过WSL使用。

5. 与其他工具集成

可与数据库、Web服务器、云服务等其他工具无缝集成。


环境准备

检查当前Shell类型

echo $SHELL

ps

查找Bash路径

which bash

常见路径:/bin/bash/usr/bin/bash

创建脚本文件

touch myscript.sh
chmod +x myscript.sh

第一个Shell脚本

Shebang(#!)

每个Shell脚本的第一行应该是shebang,它告诉系统使用哪个解释器来执行脚本:

#!/bin/bash

Hello World示例

创建文件 hello.sh

#!/bin/bash

# 这是一个注释
echo "Hello, World!"
echo "当前日期: $(date)"
echo "当前用户: $USER"
echo "当前目录: $(pwd)"

执行脚本

# 方法1:直接执行(需要执行权限)
./hello.sh

# 方法2:使用bash命令
bash hello.sh

# 方法3:使用sh命令
sh hello.sh

授予执行权限

chmod +x hello.sh

变量与数据类型

变量定义

#!/bin/bash

# 字符串变量
name="张三"
greeting='Hello'

# 数字变量
age=25
count=100

# 命令替换
current_date=$(date +%Y-%m-%d)
file_count=$(ls -1 | wc -l)

# 使用变量
echo "姓名: $name"
echo "年龄: ${age}"
echo "日期: $current_date"
echo "文件数: $file_count"

变量命名规则

  1. 只能包含字母、数字和下划线
  2. 不能以数字开头
  3. 区分大小写
  4. 等号两边不能有空格
# 正确
my_var=10
MY_VAR=20
_my_var=30

# 错误
my-var=40    # 包含连字符
my var=50    # 包含空格
1var=60      # 以数字开头

只读变量

#!/bin/bash

readonly PI=3.14159
readonly -r VERSION="1.0.0"

# 以下会报错
# PI=3.14  # 错误:只读变量

删除变量

#!/bin/bash

temp="temporary"
echo $temp  # 输出: temporary

unset temp
echo $temp  # 输出: 空

特殊变量

变量说明
$0脚本名称
$1 - $9第1到第9个参数
${10}第10个及以后的参数
$#参数个数
$*所有参数(单个字符串)
$@所有参数(数组)
$?上一个命令的退出状态
$$当前进程ID
$!最后一个后台进程的PID

示例:

#!/bin/bash

echo "脚本名: $0"
echo "参数1: $1"
echo "参数2: $2"
echo "参数总数: $#"
echo "所有参数: $*"
echo "进程ID: $$"

执行:

./script.sh arg1 arg2 arg3

条件判断

if语句

#!/bin/bash

age=18

if [ $age -ge 18 ]; then
    echo "已成年"
fi

if-else语句

#!/bin/bash

score=85

if [ $score -ge 60 ]; then
    echo "及格"
else
    echo "不及格"
fi

if-elif-else语句

#!/bin/bash

score=85

if [ $score -ge 90 ]; then
    echo "优秀"
elif [ $score -ge 80 ]; then
    echo "良好"
elif [ $score -ge 60 ]; then
    echo "及格"
else
    echo "不及格"
fi

比较运算符

数字比较
运算符说明
-eq等于 (equal)
-ne不等于 (not equal)
-gt大于 (greater than)
-ge大于等于 (greater or equal)
-lt小于 (less than)
-le小于等于 (less or equal)
字符串比较
运算符说明
=字符串相等
!=字符串不相等
-z字符串为空
-n字符串不为空
文件测试
运算符说明
-e文件存在
-f是普通文件
-d是目录
-r可读
-w可写
-x可执行
-s文件大小大于0

逻辑运算符

#!/bin/bash

age=25
has_license=true

# AND 逻辑
if [ $age -ge 18 ] && [ "$has_license" = true ]; then
    echo "可以开车"
fi

# OR 逻辑
if [ $age -lt 18 ] || [ $age -ge 65 ]; then
    echo "享受优惠票价"
fi

# NOT 逻辑
if [ ! -f "config.txt" ]; then
    echo "配置文件不存在"
fi

case语句

#!/bin/bash

echo "请选择操作:"
echo "1) 启动服务"
echo "2) 停止服务"
echo "3) 重启服务"
read choice

case $choice in
    1)
        echo "正在启动服务..."
        ;;
    2)
        echo "正在停止服务..."
        ;;
    3)
        echo "正在重启服务..."
        ;;
    *)
        echo "无效选择"
        ;;
esac

循环结构

for循环

基本语法
#!/bin/bash

# 遍历数字
for i in 1 2 3 4 5; do
    echo "数字: $i"
done

# 使用seq命令
for i in $(seq 1 5); do
    echo "数字: $i"
done

# C风格for循环
for ((i=1; i<=5; i++)); do
    echo "数字: $i"
done
遍历文件
#!/bin/bash

# 遍历当前目录的所有.txt文件
for file in *.txt; do
    echo "处理文件: $file"
done

# 遍历指定目录
for file in /var/log/*.log; do
    echo "日志文件: $file"
done
遍历数组
#!/bin/bash

fruits=("苹果" "香蕉" "橙子" "葡萄")

for fruit in "${fruits[@]}"; do
    echo "水果: $fruit"
done

while循环

#!/bin/bash

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

# 读取文件
while IFS= read -r line; do
    echo "行内容: $line"
done < "file.txt"

# 无限循环
while true; do
    echo "运行中..."
    sleep 1
done

until循环

until循环在条件为假时执行,条件为真时停止:

#!/bin/bash

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

break和continue

#!/bin/bash

# break - 跳出循环
for i in $(seq 1 10); do
    if [ $i -eq 5 ]; then
        echo "遇到5,跳出循环"
        break
    fi
    echo "数字: $i"
done

# continue - 跳过本次循环
for i in $(seq 1 10); do
    if [ $((i % 2)) -eq 0 ]; then
        continue  # 跳过偶数
    fi
    echo "奇数: $i"
done

函数

定义和调用函数

#!/bin/bash

# 定义函数
greeting() {
    echo "你好, $1!"
}

# 调用函数
greeting "张三"
greeting "李四"

带返回值的函数

#!/bin/bash

# 返回状态码(0-255)
check_file() {
    if [ -f "$1" ]; then
        return 0  # 成功
    else
        return 1  # 失败
    fi
}

check_file "test.txt"
if [ $? -eq 0 ]; then
    echo "文件存在"
else
    echo "文件不存在"
fi

# 返回字符串(通过echo)
get_date() {
    echo $(date +%Y-%m-%d)
}

current_date=$(get_date)
echo "今天日期: $current_date"

函数参数

#!/bin/bash

add() {
    local a=$1
    local b=$2
    local sum=$((a + b))
    echo "$sum"
}

result=$(add 10 20)
echo "结果: $result"

注意:使用 local 关键字定义局部变量,避免污染全局命名空间。

函数库

创建一个函数库文件 functions.sh

#!/bin/bash

# 日志函数
log_info() {
    echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2
}

# 检查命令是否存在
command_exists() {
    command -v "$1" >/dev/null 2>&1
}

在主脚本中使用:

#!/bin/bash

# 导入函数库
source ./functions.sh
# 或 . ./functions.sh

log_info "脚本开始执行"

if command_exists "git"; then
    log_info "Git已安装"
else
    log_error "Git未安装"
fi

输入输出重定向

标准流

  • 标准输入 (stdin) - 文件描述符: 0
  • 标准输出 (stdout) - 文件描述符: 1
  • 标准错误 (stderr) - 文件描述符: 2

重定向操作符

#!/bin/bash

# 输出重定向(覆盖)
echo "Hello" > output.txt

# 输出重定向(追加)
echo "World" >> output.txt

# 错误重定向
ls nonexistent_dir 2> error.log

# 标准输出和错误输出都重定向
command > output.log 2>&1

# 或(Bash 4+)
command &> output.log

# 输入重定向
sort < input.txt

# Here Document
cat << EOF
这是一段
多行文本
EOF

管道

管道将一个命令的输出作为另一个命令的输入:

#!/bin/bash

# 查看进程
ps aux | grep nginx

# 统计行数
cat file.txt | wc -l

# 排序去重
cat file.txt | sort | uniq

# 复杂管道
ps aux | grep python | grep -v grep | awk '{print $2}'

tee命令

同时输出到屏幕和文件:

#!/bin/bash

# 输出到屏幕并保存到文件
echo "Hello World" | tee output.txt

# 追加模式
echo "Another line" | tee -a output.txt

调试技巧

调试选项

#!/bin/bash

# 在shebang中启用调试
#!/bin/bash -x

# 或使用set命令
set -x  # 开启调试
# 你的代码
set +x  # 关闭调试

常用调试选项

选项说明
-x显示每个命令及其参数
-v显示Shell读取的输入行
-n只读取命令,不执行(语法检查)
-e命令失败时立即退出
-u使用未定义变量时报错

推荐的最佳实践

#!/bin/bash
set -euo pipefail

# -e: 命令失败时退出
# -u: 未定义变量报错
# -o pipefail: 管道中任何命令失败都返回错误

调试示例

#!/bin/bash
set -x

name="张三"
age=25

echo "姓名: $name"
echo "年龄: $age"

set +x
echo "调试模式已关闭"

实用示例

示例1:备份脚本

#!/bin/bash
set -euo pipefail

# 配置
BACKUP_DIR="/backup"
SOURCE_DIR="/data"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/backup_${DATE}.tar.gz"

# 创建备份目录
mkdir -p "$BACKUP_DIR"

# 执行备份
echo "开始备份: $SOURCE_DIR"
tar -czf "$BACKUP_FILE" -C "$(dirname $SOURCE_DIR)" "$(basename $SOURCE_DIR)"

# 检查备份是否成功
if [ $? -eq 0 ]; then
    echo "备份成功: $BACKUP_FILE"
    echo "文件大小: $(du -h $BACKUP_FILE | cut -f1)"
else
    echo "备份失败!" >&2
    exit 1
fi

# 删除7天前的备份
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +7 -delete
echo "已清理旧备份文件"

示例2:日志分析脚本

#!/bin/bash

LOG_FILE="/var/log/nginx/access.log"

if [ ! -f "$LOG_FILE" ]; then
    echo "日志文件不存在: $LOG_FILE"
    exit 1
fi

echo "=== Nginx访问日志分析 ==="
echo ""

# 访问量Top 10 IP
echo "📊 Top 10 访问IP:"
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -10
echo ""

# 访问量Top 10 URL
echo "📊 Top 10 访问URL:"
awk '{print $7}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -10
echo ""

# HTTP状态码统计
echo "📊 HTTP状态码统计:"
awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -nr
echo ""

# 今日访问量
TODAY=$(date +%d/%b/%Y)
TODAY_COUNT=$(grep -c "$TODAY" "$LOG_FILE")
echo "📊 今日访问量: $TODAY_COUNT"

示例3:批量重命名文件

#!/bin/bash

# 检查参数
if [ $# -ne 2 ]; then
    echo "用法: $0 <旧扩展名> <新扩展名>"
    echo "示例: $0 .jpg .png"
    exit 1
fi

OLD_EXT=$1
NEW_EXT=$2
COUNT=0

# 批量重命名
for file in *"$OLD_EXT"; do
    # 检查文件是否存在
    if [ -f "$file" ]; then
        new_name="${file%$OLD_EXT}$NEW_EXT"
        mv "$file" "$new_name"
        echo "重命名: $file -> $new_name"
        ((COUNT++))
    fi
done

echo "完成! 共重命名 $COUNT 个文件"

示例4:系统监控脚本

#!/bin/bash

echo "========== 系统监控报告 =========="
echo "时间: $(date)"
echo ""

# CPU使用率
echo "📊 CPU使用率:"
top -bn1 | grep "Cpu(s)" | awk '{print "使用率: " 100 - $8 "%"}'
echo ""

# 内存使用
echo "📊 内存使用:"
free -h | grep Mem | awk '{printf "总计: %s, 已用: %s, 可用: %s, 使用率: %.1f%%\n", $2, $3, $4, $3/$2*100}'
echo ""

# 磁盘使用
echo "📊 磁盘使用:"
df -h | grep -E '^/dev/' | awk '{printf "%s - 总计: %s, 已用: %s, 可用: %s, 使用率: %s\n", $6, $2, $3, $4, $5}'
echo ""

# 系统负载
echo "📊 系统负载:"
uptime | awk -F'load average:' '{print $2}'
echo ""

# 登录用户
echo "📊 当前登录用户:"
who
echo ""

echo "========== 报告结束 =========="

最佳实践

1. 始终使用Shebang

#!/bin/bash

2. 启用严格模式

set -euo pipefail

3. 使用引号包裹变量

# 推荐
echo "$variable"
if [ -f "$file" ]; then

# 不推荐(可能在空格处断开)
echo $variable

4. 使用函数组织代码

#!/bin/bash

main() {
    # 主逻辑
}

main "$@"

5. 添加注释和文档

#!/bin/bash
#
# 脚本名称: backup.sh
# 描述: 自动备份指定目录
# 用法: ./backup.sh [目录路径]
# 作者: Your Name
# 日期: 2024-01-01

6. 检查命令是否存在

if ! command -v git &> /dev/null; then
    echo "错误: git未安装"
    exit 1
fi

7. 使用临时文件时清理

#!/bin/bash

TEMP_FILE=$(mktemp)
trap "rm -f $TEMP_FILE" EXIT  # 脚本退出时清理

# 使用临时文件
echo "data" > "$TEMP_FILE"

8. 处理用户输入

#!/bin/bash

read -p "确认删除? (y/n): " confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo "操作已取消"
    exit 0
fi

9. 使用颜色输出

#!/bin/bash

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

echo -e "${GREEN}成功${NC}"
echo -e "${RED}错误${NC}"
echo -e "${YELLOW}警告${NC}"

10. 记录日志

#!/bin/bash

LOG_FILE="/var/log/myscript.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "脚本开始执行"

学习资源

在线教程

推荐书籍

  • 《Linux命令行与shell脚本编程大全》
  • 《Advanced Bash-Scripting Guide》
  • 《Shell脚本学习指南》

实践建议

  1. 从简单的脚本开始
  2. 多阅读优秀脚本
  3. 善用 man 命令查看文档
  4. 使用 shellcheck 检查脚本语法
  5. 在测试环境中先验证

总结

Shell脚本是系统管理和自动化任务的强大工具。通过本指南,你已经学习了:

  • ✅ Shell脚本基础概念
  • ✅ 变量和运算符
  • ✅ 条件判断和循环
  • ✅ 函数定义和使用
  • ✅ 输入输出重定向
  • ✅ 调试技巧
  • ✅ 实用示例和最佳实践

下一步:开始编写自己的脚本,从简单的自动化任务开始,逐步掌握更高级的技巧。记住,实践是最好的学习方式!


祝学习愉快!🚀