基础语法

Shebang 和执行

#!/bin/bash
# 或
#!/usr/bin/env bash

# 执行方式
chmod +x script.sh
./script.sh

# 或直接执行
bash script.sh

变量

# 赋值(等号两边不能有空格)
name="John"
age=25
readonly PI=3.14  # 只读变量

# 使用变量
echo "Name: $name"
echo "Name: ${name}"
echo "Length: ${#name}"  # 字符串长度

# 默认值
${var:-default}    # var 未定义或为空时使用 default
${var:=default}    # var 未定义或为空时赋值为 default
${var:+value}      # var 已定义且非空时使用 value
${var:?error msg}  # var 未定义或为空时报错

# 字符串操作
${string:offset:length}  # 子串
${string#pattern}        # 删除左边最短匹配
${string##pattern}       # 删除左边最长匹配
${string%pattern}        # 删除右边最短匹配
${string%%pattern}       # 删除右边最长匹配
${string/pattern/replacement}  # 替换

数组

# 索引数组
arr=(apple banana cherry)
arr[0]="apple"
arr[1]="banana"

# 访问
echo ${arr[0]}      # 第一个元素
echo ${arr[@]}      # 所有元素
echo ${#arr[@]}     # 数组长度
echo ${arr[@]:1:2}  # 从索引1开始取2个

# 关联数组
declare -A map
map[name]="John"
map[age]=25

echo ${map[name]}
echo ${!map[@]}  # 所有键
echo ${map[@]}   # 所有值

流程控制

条件判断

# if 语句
if [ condition ]; then
    # commands
elif [ condition ]; then
    # commands
else
    # commands
fi

# 常用条件
[ -f file ]      # 文件存在且是普通文件
[ -d dir ]       # 目录存在
[ -r file ]      # 文件可读
[ -w file ]      # 文件可写
[ -x file ]      # 文件可执行
[ -s file ]      # 文件非空
[ -z string ]    # 字符串为空
[ -n string ]    # 字符串非空
[ "$a" = "$b" ]  # 字符串相等
[ "$a" != "$b" ] # 字符串不相等
[ $a -eq $b ]    # 数值相等
[ $a -gt $b ]    # 数值大于
[ $a -lt $b ]    # 数值小于
[ $a -ge $b ]    # 数值大于等于
[ $a -le $b ]    # 数值小于等于

# [[ ]] 增强版(支持正则)
[[ $string =~ pattern ]]  # 正则匹配
[[ $a == $b ]]            # 字符串比较
[[ $a -gt $b && $c -lt $d ]]  # 逻辑与

# case 语句
case $var in
    pattern1)
        # commands
        ;;
    pattern2)
        # commands
        ;;
    *)
        # default
        ;;
esac

循环

# for 循环
for i in 1 2 3 4 5; do
    echo $i
done

# C 风格 for
for ((i=0; i<10; i++)); do
    echo $i
done

# 范围
for i in {1..10}; do
    echo $i
done

# 带步长
for i in {0..100..5}; do
    echo $i
done

# 遍历文件
for file in /var/log/*.log; do
    echo "Processing: $file"
done

# while 循环
while [ condition ]; do
    # commands
done

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

# 无限循环
while true; do
    # commands
    sleep 1
done

# until 循环(条件为假时执行)
until [ condition ]; do
    # commands
done

# break 和 continue
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        continue  # 跳过本次
    fi
    if [ $i -eq 8 ]; then
        break  # 跳出循环
    fi
    echo $i
done

函数

# 定义函数
function greet() {
    echo "Hello, $1!"
}

# 或简写
greet() {
    echo "Hello, $1!"
}

# 调用
greet "John"

# 返回值
add() {
    echo $(( $1 + $2 ))
}
result=$(add 3 5)

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

if check_file "/etc/passwd"; then
    echo "File exists"
fi

# 局部变量
myfunc() {
    local var="local"
    echo $var
}

文本处理

grep

# 基本搜索
grep "pattern" file.txt

# 选项
-i          # 忽略大小写
-r          # 递归搜索
-n          # 显示行号
-c          # 只显示匹配数量
-l          # 只显示文件名
-v          # 反向匹配
-w          # 全词匹配
-e          # 多个模式
-E          # 扩展正则
-P          # Perl 正则

# 示例
grep -rn "error" /var/log/
grep -i "warning" *.log
grep -E "^[0-9]{4}-[0-9]{2}" log.txt

sed

# 替换
sed 's/old/new/' file.txt
sed 's/old/new/g' file.txt  # 全局替换
sed -i 's/old/new/g' file.txt  # 原地修改

# 删除
sed '/pattern/d' file.txt
sed '1,5d' file.txt  # 删除1-5行

# 插入
sed '3i\new line' file.txt  # 在第3行前插入
sed '3a\new line' file.txt  # 在第3行后插入

# 打印
sed -n '1,5p' file.txt  # 打印1-5行
sed -n '/pattern/p' file.txt  # 打印匹配行

# 常用示例
sed 's/[[:space:]]*$//' file.txt  # 删除行尾空格
sed '/^$/d' file.txt  # 删除空行
sed 's/^[[:space:]]*//' file.txt  # 删除行首空格

awk

# 基本语法
awk '{print $1, $3}' file.txt  # 打印第1和第3列

# 条件过滤
awk '$3 > 100' file.txt
awk '/pattern/ {print $1}' file.txt

# 内置变量
NR      # 当前行号
NF      # 当前行字段数
$0      # 整行
$1-$n   # 第1到第n个字段

# BEGIN/END
awk 'BEGIN {print "Start"} {print $1} END {print "End"}' file.txt

# 计算
awk '{sum += $1} END {print sum}' file.txt
awk '{avg += $1} END {print avg/NR}' file.txt

# 格式化输出
awk '{printf "%-20s %10d\n", $1, $2}' file.txt

# 字段分隔符
awk -F: '{print $1, $3}' /etc/passwd
awk -F',' '{print $1}' data.csv

cut

# 按字符
cut -c1-5 file.txt

# 按字段
cut -d: -f1,3 /etc/passwd

# 按字节
cut -b1-10 file.txt

sort

sort file.txt            # 字母排序
sort -n file.txt         # 数值排序
sort -r file.txt         # 逆序
sort -k2 -t: file.txt    # 按第2列排序,分隔符为:
sort -u file.txt         # 去重
sort -h file.txt         # 人类可读排序(1K, 2M, 3G)

uniq

sort file.txt | uniq      # 去重
sort file.txt | uniq -c   # 计数
sort file.txt | uniq -d   # 只显示重复行
sort file.txt | uniq -u   # 只显示唯一行

高级特性

进程替换

# 比较两个命令的输出
diff <(ls /dir1) <(ls /dir2)

# 同时读取多个文件
while read line1 <&3 && read line2 <&4; do
    echo "File1: $line1, File2: $line2"
done 3< file1.txt 4< file2.txt

陷阱处理

# 清理临时文件
cleanup() {
    rm -f "$tmpfile"
    echo "Cleaned up"
}
trap cleanup EXIT

# 忽略信号
trap '' SIGINT SIGTERM

# 捕获错误
trap 'echo "Error on line $LINENO"' ERR

Here Document

# 基本用法
cat << EOF
This is a heredoc
Variable: $name
EOF

# 不解析变量
cat << 'EOF'
This is a heredoc
Variable: $name will not be expanded
EOF

# 缩进
cat <<- EOF
This heredoc allows tabs for indentation
EOF

子 Shell

# 在子 Shell 中执行
(
    cd /tmp
    ls -la
)
# 当前目录不变

# 命令组
{
    echo "Line 1"
    echo "Line 2"
} > output.txt

实战案例

系统监控脚本

#!/bin/bash

LOG_FILE="/var/log/monitor.log"

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE"
}

check_disk() {
    usage=$(df -h / | awk 'NR==2{print $5}' | tr -d '%')
    if [ "$usage" -gt 80 ]; then
        log "WARNING: Disk usage is ${usage}%"
    fi
}

check_memory() {
    free -m | awk 'NR==2{printf "Memory: %.1f%%\n", $3*100/$2}'
}

check_load() {
    load=$(uptime | awk -F'load average:' '{print $2}' | awk -F, '{print $1}')
    if [ $(echo "$load > 2.0" | bc) -eq 1 ]; then
        log "WARNING: Load average is $load"
    fi
}

while true; do
    check_disk
    check_memory
    check_load
    sleep 60
done

自动备份脚本

#!/bin/bash

BACKUP_DIR="/backup"
SOURCE_DIR="/var/www"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/backup_$DATE.tar.gz"

mkdir -p "$BACKUP_DIR"

tar -czf "$BACKUP_FILE" "$SOURCE_DIR"

# 保留最近7天的备份
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +7 -delete

echo "Backup completed: $BACKUP_FILE"

批量重命名

#!/bin/bash

# 批量添加前缀
for file in *.txt; do
    mv "$file" "prefix_$file"
done

# 批量修改扩展名
for file in *.jpeg; do
    mv "$file" "${file%.jpeg}.jpg"
done

# 批量重命名为序号
i=1
for file in *.jpg; do
    mv "$file" "$(printf '%03d' $i).jpg"
    ((i++))
done

日志分析

#!/bin/bash

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

echo "=== 访问统计 ==="
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10

echo ""
echo "=== 错误统计 ==="
grep -c "500\|502\|503" "$LOG_FILE"

echo ""
echo "=== 每小时访问量 ==="
awk '{print $4}' "$LOG_FILE" | cut -d: -f2 | sort | uniq -c | sort -n

面试常见问题

  1. $@$* 的区别?

    • $@: 每个参数作为独立字符串
    • $*: 所有参数作为一个字符串
    • 建议始终使用 "$@"
  2. [[ ]][ ] 的区别?

    • [[ ]] 是 bash 增强版
    • 支持正则、模式匹配
    • 更安全,不需要引号
  3. 如何捕获命令输出?

    • var=$(command): 捕获标准输出
    • var=$(command 2>&1): 捕获所有输出
    • command | var: 管道捕获
  4. 如何调试 bash 脚本?

    • bash -x script.sh: 显示执行过程
    • set -x: 在脚本中开启调试
    • set -e: 遇到错误立即退出
    • set -u: 使用未定义变量时报错