Skip to content

Shell 脚本

Shell 脚本基础

脚本结构

bash
#!/bin/bash

echo "Hello, World!"
  • #!/bin/bash - Shebang,指定解释器
  • .sh 为扩展名

执行脚本

bash
chmod +x script.sh                 # 添加执行权限
./script.sh                        # 执行脚本
bash script.sh                     # 指定解释器执行
source script.sh                   # 在当前 Shell 执行
. script.sh                        # 同 source
bash -x script.sh                  # 调试模式
bash -n script.sh                  # 语法检查

注释

bash
#!/bin/bash

# 单行注释

: '
多行注释
第二行
第三行
'

echo "Done"

变量

变量定义与使用

bash
name="Linux"                       # 定义变量(等号两边不能有空格)
echo $name                         # 使用变量
echo ${name}                       # 推荐用法
echo "Hello, ${name}!"             # 在字符串中使用

unset name                         # 删除变量
readonly PI=3.14                   # 只读变量

变量类型

bash
string="Hello"                     # 字符串
number=42                          # 数字(实际是字符串)
array=(one two three)              # 数组
declare -i num=10                  # 整数
declare -a arr                     # 数组
declare -A assoc                   # 关联数组
declare -r CONST="value"           # 只读
declare -x VAR="value"             # 导出为环境变量

特殊变量

变量说明
$0脚本名称
$1-$n位置参数
$#参数个数
$@所有参数(数组)
$*所有参数(字符串)
$?上一命令退出状态
$$当前进程 PID
$!后台进程 PID
$_上一命令最后一个参数
bash
#!/bin/bash
echo "Script: $0"
echo "First arg: $1"
echo "Args count: $#"
echo "All args: $@"
echo "Exit status: $?"

环境变量

bash
export VAR="value"                 # 导出环境变量
unset VAR                          # 删除环境变量
env                                # 显示所有环境变量
printenv                           # 显示环境变量
printenv PATH                      # 显示指定变量
echo $PATH                         # 使用环境变量

读取输入

bash
read name                          # 读取输入
read -p "Enter name: " name        # 带提示
read -s -p "Password: " pass       # 静默输入
read -t 10 name                    # 超时10秒
read -n 1 char                     # 读取一个字符
read -a arr                        # 读取到数组
read -d ':' line                   # 以冒号分隔
IFS=',' read -a arr <<< "a,b,c"    # 指定分隔符

字符串操作

字符串定义

bash
str1='Hello'                       # 单引号(原样输出)
str2="Hello, $name"                # 双引号(解析变量)
str3="Line 1
Line 2"                            # 多行字符串
str4=$(command)                    # 命令替换
str5=`command`                     # 旧式命令替换

字符串长度

bash
str="Hello"
echo ${#str}                       # 输出: 5

子字符串

bash
str="Hello World"
echo ${str:0:5}                    # 从位置0开始,取5个字符: Hello
echo ${str:6}                      # 从位置6开始到结尾: World
echo ${str: -5}                    # 最后5个字符: World
echo ${str:(-5)}                   # 同上

字符串替换

bash
str="Hello World World"
echo ${str/World/Linux}            # 替换第一个: Hello Linux World
echo ${str//World/Linux}           # 替换所有: Hello Linux Linux
echo ${str/#Hello/Hi}              # 替换开头: Hi World World
echo ${str/%World/End}             # 替换结尾: Hello World End

字符串删除

bash
str="/path/to/file.txt"
echo ${str#*/}                     # 删除最短前缀: path/to/file.txt
echo ${str##*/}                    # 删除最长前缀: file.txt
echo ${str%/*}                     # 删除最短后缀: /path/to
echo ${str%%/*}                    # 删除最长后缀: (空)

字符串大小写

bash
str="Hello World"
echo ${str^^}                      # 转大写: HELLO WORLD
echo ${str,,}                      # 转小写: hello world
echo ${str^}                       # 首字母大写: Hello World
echo ${str,}                       # 首字母小写: hello World

数组

数组定义

bash
arr=(one two three)                # 定义数组
arr[0]="one"                       # 逐个赋值
arr[1]="two"
arr[2]="three"
declare -a arr                     # 声明数组
arr=([0]=one [2]=three)            # 指定索引

数组访问

bash
arr=(one two three four)
echo ${arr[0]}                     # 第一个元素: one
echo ${arr[-1]}                    # 最后一个元素: four
echo ${arr[@]}                     # 所有元素
echo ${arr[*]}                     # 所有元素
echo ${#arr[@]}                    # 元素个数
echo ${#arr}                       # 第一个元素长度
echo ${!arr[@]}                    # 所有索引
echo ${arr[@]:1:2}                 # 从索引1开始取2个: two three

数组操作

bash
arr=(one two three)
arr+=("four")                      # 添加元素
arr+=(five six)                    # 添加多个
unset arr[1]                       # 删除元素
arr=("${arr[@]}")                  # 重建索引

for i in "${arr[@]}"; do
    echo "$i"
done

关联数组

bash
declare -A user
user[name]="John"
user[age]=30
user[city]="Beijing"

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

for key in "${!user[@]}"; do
    echo "$key: ${user[$key]}"
done

运算符

算术运算

bash
a=10
b=3

echo $((a + b))                    # 加法: 13
echo $((a - b))                    # 减法: 7
echo $((a * b))                    # 乘法: 30
echo $((a / b))                    # 除法: 3
echo $((a % b))                    # 取余: 1
echo $((a ** b))                   # 幂运算: 1000

echo $((a++))                      # 自增
echo $((a--))                      # 自减
echo $((++a))                      # 先自增
echo $((--a))                      # 先自减

let a=10+3                         # let 命令
expr 10 + 3                        # expr 命令

比较运算

数字比较:

运算符说明
-eq等于
-ne不等于
-gt大于
-lt小于
-ge大于等于
-le小于等于
bash
[ $a -eq $b ]
(( a == b ))
(( a > b ))
(( a < b ))

字符串比较:

运算符说明
=相等
!=不相等
-z为空
-n非空
bash
[ "$str1" = "$str2" ]
[ "$str1" != "$str2" ]
[ -z "$str" ]
[ -n "$str" ]
[[ "$str" == "pattern"* ]]         # 模式匹配
[[ "$str" =~ regex ]]              # 正则匹配

文件测试

运算符说明
-e存在
-f是文件
-d是目录
-r可读
-w可写
-x可执行
-s非空
-L符号链接
-nt比...新
-ot比...旧
bash
[ -f file.txt ]
[ -d /path/dir ]
[ -x script.sh ]
[ file1 -nt file2 ]

逻辑运算

bash
[ condition1 ] && [ condition2 ]   # 与
[ condition1 ] || [ condition2 ]   # 或
[ ! condition ]                    # 非

[[ condition1 && condition2 ]]
[[ condition1 || condition2 ]]
(( a > 0 && b > 0 ))
(( a > 0 || b > 0 ))

条件判断

if 语句

bash
if [ condition ]; then
    commands
fi

if [ condition ]; then
    commands
else
    commands
fi

if [ condition1 ]; then
    commands1
elif [ condition2 ]; then
    commands2
else
    commands3
fi

示例:

bash
#!/bin/bash
age=25

if [ $age -lt 18 ]; then
    echo "未成年"
elif [ $age -lt 60 ]; then
    echo "成年"
else
    echo "老年"
fi

if [ -f "/etc/passwd" ]; then
    echo "文件存在"
fi

if [[ "$str" =~ ^[0-9]+$ ]]; then
    echo "是数字"
fi

case 语句

bash
case $var in
    pattern1)
        commands
        ;;
    pattern2)
        commands
        ;;
    *)
        commands
        ;;
esac

示例:

bash
#!/bin/bash
read -p "Enter a number (1-3): " num

case $num in
    1)
        echo "One"
        ;;
    2)
        echo "Two"
        ;;
    3)
        echo "Three"
        ;;
    *)
        echo "Invalid"
        ;;
esac

case $answer in
    y|Y|yes|YES)
        echo "Yes"
        ;;
    n|N|no|NO)
        echo "No"
        ;;
    *)
        echo "Invalid"
        ;;
esac

循环

for 循环

bash
for var in list; do
    commands
done

for (( i=0; i<10; i++ )); do
    commands
done

示例:

bash
for i in 1 2 3 4 5; do
    echo $i
done

for i in {1..5}; do
    echo $i
done

for i in {1..10..2}; do            # 步长为2
    echo $i
done

for file in *.txt; do
    echo $file
done

for file in $(ls); do
    echo $file
done

for (( i=0; i<5; i++ )); do
    echo $i
done

arr=(one two three)
for item in "${arr[@]}"; do
    echo $item
done

for i in "${!arr[@]}"; do
    echo "$i: ${arr[$i]}"
done

while 循环

bash
while [ condition ]; do
    commands
done

示例:

bash
i=1
while [ $i -le 5 ]; do
    echo $i
    ((i++))
done

while read line; do
    echo $line
done < file.txt

while IFS=',' read -r col1 col2 col3; do
    echo "$col1 - $col2 - $col3"
done < data.csv

while true; do
    echo "Running..."
    sleep 1
done

until 循环

bash
until [ condition ]; do
    commands
done

示例:

bash
i=1
until [ $i -gt 5 ]; do
    echo $i
    ((i++))
done

循环控制

bash
for i in {1..10}; do
    if [ $i -eq 5 ]; then
        break                      # 跳出循环
    fi
    echo $i
done

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

函数

函数定义

bash
function_name() {
    commands
    return value
}

function function_name() {
    commands
}

函数调用

bash
#!/bin/bash

greet() {
    echo "Hello, $1!"
}

greet "World"                      # 调用函数

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

result=$(add 10 20)
echo "Result: $result"

return_value() {
    return 42
}

return_value
echo $?                             # 获取返回值

函数参数

bash
func() {
    echo "Function: $0"            # 脚本名
    echo "First arg: $1"
    echo "All args: $@"
    echo "Args count: $#"
}

func arg1 arg2 arg3

局部变量

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

var="global"
func
echo $var                          # 输出: global

递归函数

bash
factorial() {
    if [ $1 -le 1 ]; then
        echo 1
    else
        local prev=$(factorial $(( $1 - 1 )))
        echo $(( $1 * prev ))
    fi
}

result=$(factorial 5)
echo $result                        # 输出: 120

输入输出

输出

bash
echo "Hello"                       # 输出
echo -n "No newline"               # 不换行
echo -e "Line1\nLine2"             # 解释转义字符
printf "%-10s %5d\n" "Name" 25     # 格式化输出

重定向

bash
command > file                     # 输出到文件(覆盖)
command >> file                    # 输出到文件(追加)
command 2> file                    # 错误输出到文件
command > file 2>&1                # 合并输出
command &> file                    # 合并输出(简写)
command < file                     # 从文件输入
command << EOF                     # Here Document
line1
line2
EOF
command <<< "string"               # Here String

管道

bash
command1 | command2                # 管道
command1 | command2 | command3     # 多级管道
command1 | tee file | command2     # 同时输出到文件和管道
command1 | xargs command2          # 将输出作为参数

调试

调试选项

bash
bash -n script.sh                  # 语法检查
bash -x script.sh                  # 显示执行的命令
bash -v script.sh                  # 显示读取的行
bash -xv script.sh                 # 组合使用

脚本内调试

bash
#!/bin/bash
set -x                             # 开启调试
set +x                             # 关闭调试
set -e                             # 命令失败时退出
set -u                             # 使用未定义变量时报错
set -o pipefail                    # 管道中任一命令失败时退出
set -euxo pipefail                 # 常用组合

trap 捕获信号

bash
#!/bin/bash
cleanup() {
    echo "Cleaning up..."
    rm -f /tmp/tempfile
}

trap cleanup EXIT                  # 退出时执行
trap 'echo Interrupted' INT        # Ctrl+C 时执行
trap 'echo Terminated' TERM        # kill 时执行
trap - INT                         # 取消捕获

实用脚本示例

参数处理

bash
#!/bin/bash
while getopts "a:b:c" opt; do
    case $opt in
        a) arg_a="$OPTARG" ;;
        b) arg_b="$OPTARG" ;;
        c) flag_c=true ;;
        \?) echo "Invalid option: -$OPTARG" >&2; exit 1 ;;
        :) echo "Option -$OPTARG requires argument" >&2; exit 1 ;;
    esac
done
shift $((OPTIND-1))

echo "a: $arg_a"
echo "b: $arg_b"
echo "c: $flag_c"
echo "Remaining: $@"

文件备份

bash
#!/bin/bash
backup_dir="/backup"
date=$(date +%Y%m%d)
source_dir=$1

if [ -z "$source_dir" ]; then
    echo "Usage: $0 <source_dir>"
    exit 1
fi

tar -czf "${backup_dir}/backup_${date}.tar.gz" "$source_dir"
echo "Backup completed: ${backup_dir}/backup_${date}.tar.gz"

日志分析

bash
#!/bin/bash
log_file="/var/log/app.log"
report_file="report_$(date +%Y%m%d).txt"

echo "=== Log Report $(date) ===" > "$report_file"
echo "" >> "$report_file"

echo "Error count:" >> "$report_file"
grep -c "ERROR" "$log_file" >> "$report_file"

echo "" >> "$report_file"
echo "Top 10 IPs:" >> "$report_file"
awk '{print $1}' "$log_file" | sort | uniq -c | sort -rn | head -10 >> "$report_file"

echo "Report generated: $report_file"

下一步学习