shell学习从入门到精通(第二部分)
核心语法
1. 条件判断 (if-elif-else
)
让脚本根据不同条件执行不同操作。
基本结构:
if [ condition ]; then# command to execute if condition is true elif [ another_condition ]; then# command to execute if another_condition is true else# command to execute if all conditions are false fi
关键点:
if 和 [ 之间必须有空格。
[ 和 condition 之间以及 condition 和 ] 之间也必须有空格。
then 必须单独一行,或者用分号 ; 与 if 语句放在同一行:if [ condition ]; then ...
推荐使用 [[ ... ]],它更强大且不易出错,尤其是在处理字符串时。
#!/bin/bashread -p "Enter a number: " NUMif [[ $NUM -gt 100 ]]; thenecho "$NUM is greater than 100." elif [[ $NUM -eq 100 ]]; thenecho "$NUM is exactly 100." elseecho "$NUM is less than 100." fi
字符串比较:
操作符 | 描述 |
| 等于 |
| 不等于 |
| 字符串为空 |
| 字符串不为空 |
#!/bin/bashread -p "Enter 'yes' or 'no': " ANSWERif [[ $ANSWER == "yes" ]]; thenecho "You chose 'yes'."
elif [[ $ANSWER != "no" ]]; thenecho "Invalid input."
elseecho "You chose 'no'."
fi
2. case
语句
当有多个分支选择时,case
是比 if-elif-else
更清晰的替代方案。
#!/bin/bashread -p "Enter a character (a, b, or c): " CHARcase $CHAR ina|A)echo "You entered 'a'.";;b|B)echo "You entered 'b'.";;c|C)echo "You entered 'c'.";;*)echo "Invalid character.";;
esac
关键点:
|
用于匹配多个模式。*)
是一个通配符,匹配任何其他输入。每个分支以
;;
结束。整个
case
语句以esac
(case反写) 结束。
3. 循环结构
for
循环:遍历列表:
#!/bin/bashfor FRUIT in apple banana orange; doecho "I like $FRUIT." done
遍历数字序列:
#!/bin/bashecho "Counting from 1 to 5:" for i in {1..5}; doecho $i done
C 语言风格的
for
循环:#!/bin/bashfor (( i=0; i<5; i++ )); doecho "C-style loop, iteration: $i" done
while
循环: 当条件为真时持续循环。常用于逐行读取文件。
#!/bin/bashCOUNTER=0
while [[ $COUNTER -lt 5 ]]; doecho "Counter is $COUNTER"# 必须有改变条件的语句,否则会死循环let COUNTER++
done
逐行读取文件(重要用法):
#!/bin/bashFILENAME="hello.sh"
while IFS= read -r line; doecho "Line: $line"
done < "$FILENAME"
IFS= read -r line
是读取文件的标准、安全的方式,可以防止read
命令意外地处理反斜杠和行首行尾的空白字符。
until
循环: 当条件为假时持续循环,直到条件为真。#!/bin/bashCOUNTER=0 until [[ $COUNTER -ge 5 ]]; doecho "Counter is $COUNTER"let COUNTER++ done
4. 函数 (Functions)
将代码块封装成函数,方便复用。
定义和调用:
#!/bin/bash# 定义函数 greet() {echo "Hello there!" }# 调用函数 echo "Calling the function..." greet echo "Function called."
传递参数:
在函数内部,
$1
,$2
,$3
, ... 分别代表第一个、第二个、第三个参数。$@
代表所有参数的列表。$#
代表传递给函数的参数个数。#!/bin/bashprint_info() {if [[ $# -eq 0 ]]; thenecho "Usage: print_info <name> <age>"return 1 # 返回一个非零值表示错误fiecho "Name: $1"echo "Age: $2"echo "All arguments: $@" }print_info "Bob" 42 print_info # 测试错误处理
返回值:
Shell 函数的 return 语句只返回一个 0-255 的整数,称为 退出状态码。0 通常表示成功,非 0 表示失败。
要 "返回" 数据,通常是在函数中用 echo 输出,然后在调用处用命令替换 $(...) 来捕获输出。
local 关键字使变量的作用域仅限于函数内部,这是一个好习惯。
#!/bin/bash
get_full_name() {
local first_name=$1
local last_name=$2
# 使用 echo "返回" 结果
echo "$first_name $last_name"
}
# 使用命令替换捕获函数的输出
FULL_NAME=$(get_full_name "John" "Doe")
echo "The full name is: $FULL_NAME"第三部分:高级用法
1. 数组 (Arrays)
索引数组 (Indexed Arrays):#!/bin/bash
# 定义数组
fruits=("Apple" "Banana" "Cherry")
# 访问元素(索引从0开始)
echo "First fruit: ${fruits[0]}"
# 访问所有元素
echo "All fruits: ${fruits[@]}"
# 获取数组长度
echo "Number of fruits: ${#fruits[@]}"
# 添加元素
fruits+=("Orange")
# 遍历数组
for fruit in "${fruits[@]}"; do
echo "Processing $fruit"
done
AI写代码关联数组 (Associative Arrays / Hashes): 键值对数组(需要 Bash 4.0+)。
#!/bin/bash
# 声明一个关联数组
declare -A user
# 赋值
user["name"]="Alice"
user["id"]="101"
user["email"]="alice@example.com"
# 访问元素
echo "User Name: ${user[name]}"
# 遍历所有的键
echo "All keys: ${!user[@]}"
# 遍历所有的值
echo "All values: ${user[@]}"
# 遍历键值对
for key in "${!user[@]}"; do
echo "$key: ${user[$key]}"
done
AI写代码2. 字符串处理
操作 示例 结果
长度 ${#str} 字符串长度
截取 ${str:2:3} 从索引 2 开始取 3 个字符
替换 ${str/old/new} 替换第一个 old 为 new
全替换 ${str//old/new} 替换所有 old 为 new
删除前缀 ${str#prefix} 删除最短前缀
删除后缀 ${str%suffix} 删除最短后缀
示例:#!/bin/bash
str="hello world"
echo "长度:${#str}" # 输出:11
echo "截取:${str:3:4}" # 输出:lo w
echo "替换:${str/world/shell}" # 输出:hello shell
AI写代码
3. 精细数学计算
Shell 内置的 $((...)) 只支持整数运算。对于浮点数或更复杂的计算,需要借助外部工具。整数计算:
#!/bin/bash
A=10
B=3
SUM=$((A + B))
PRODUCT=$((A * B))
REMAINDER=$((A % B)) # 取余
echo "Sum: $SUM, Product: $PRODUCT, Remainder: $REMAINDER"
AI写代码浮点数计算 (bc): bc 是一个强大的计算器,-l 参数可以加载数学库,支持高精度计算。
#!/bin/bash
# 将表达式通过管道传给 bc
RESULT=$(echo "scale=4; 10 / 3" | bc)
echo "10 / 3 = $RESULT"
# 更复杂的计算
PI=$(echo "scale=10; 4*a(1)" | bc -l) # a() 是反正切函数,4*a(1)是计算pi的经典方法
echo "Pi ≈ $PI"
VAR1=5.5
VAR2=2.2
SUM=$(echo "$VAR1 + $VAR2" | bc)
echo "$VAR1 + $VAR2 = $SUM"
AI写代码使用 awk 计算: awk 也是一个处理文本和进行计算的强大工具。
#!/bin/bash
RESULT=$(awk "BEGIN {printf \"%.4f\", 10/3}")
echo "10 / 3 = $RESULT"
AI写代码
4. 颜色和格式化输出
通过 ANSI 转义序列设置输出颜色,格式:\033[颜色代码m文本\033[0m(0m 重置颜色)常用颜色代码:
文本色:30(黑)、31(红)、32(绿)、33(黄)、34(蓝)、35(紫)、36(青)、37(白)
背景色:40(黑)、41(红)、42(绿)等
使用 ANSI escape codes 来控制终端输出的颜色和样式。语法: \e[...m 或 \033[...m
示例:
#!/bin/bash
# 为了可读性和复用,最好将颜色代码定义为变量
COLOR_RESET='\e[0m'
COLOR_RED='\e[31m'
COLOR_GREEN='\e[32m'
COLOR_YELLOW='\e[33m'
BG_BLUE='\e[44m'
STYLE_BOLD='\e[1m'
echo -e "${COLOR_RED}This is red text.${COLOR_RESET}"
echo -e "${COLOR_GREEN}This is green text.${COLOR_RESET}"
echo -e "${STYLE_BOLD}${COLOR_YELLOW}This is bold yellow text.${COLOR_RESET}"
echo -e "${BG_BLUE}This text has a blue background.${COLOR_RESET}"
# 组合使用
echo -e "${STYLE_BOLD}${COLOR_RED}${BG_BLUE}DANGER! Critical Error!${COLOR_RESET}"
AI写代码echo -e 是必须的,它让 echo 能够解释转义序列。
5. 强大的命令执行与控制
命令分组:( ... ): 在一个 子 Shell 中执行命令组。子 Shell 中的变量和目录改变不会影响父 Shell。
{ ...; }: 在 当前 Shell 中执行命令组。注意 } 前必须有分号或换行。
# 子Shell示例
echo "Before: PWD=$PWD"
(cd /tmp; echo "Inside subshell: PWD=$PWD")
echo "After: PWD=$PWD" # PWD 没变
# 当前Shell示例
echo "Before: PWD=$PWD"
{ cd /var; echo "Inside group: PWD=$PWD"; }
echo "After: PWD=$PWD" # PWD 变了
AI写代码
输入/输出重定向:>: 重定向标准输出(会覆盖文件内容)。 ls > file.txt
>>: 重定向标准输出(追加到文件末尾)。date >> file.txt
<: 重定向标准输入。 read -r line < file.txt
2>: 重定向标准错误。command_that_fails 2> error.log
&>: 重定向标准输出和标准错误。command &> all_output.log
/dev/null: 一个特殊的设备文件,所有写入它的数据都会被丢弃("黑洞")。常用于丢弃不想要的输出。command > /dev/null 2>&1
管道 (|): 将前一个命令的标准输出作为后一个命令的标准输入。这是 Shell 的精髓之一。
# 统计当前目录有多少个 .sh 文件
ls -l | grep ".sh$" | wc -l
AI写代码
进程替换 (<(command)): 这是一个非常高级的特性,它将一个命令的输出伪装成一个文件,然后可以被另一个需要文件作为输入的命令使用。# 比较两个目录下的文件列表,而无需创建临时文件
diff <(ls /bin) <(ls /usr/bin)
AI写代码
Here 文档(<<)
向命令输入多行文本(无需手动输入),语法命令 << 分界符
多行文本
分界符
AI写代码
#!/bin/bash
# 向文件写入多行内容
cat << EOF > info.txt
Name: Alice
Age: 25
City: Beijing
EOF
# info.txt 内容为上述三行
# 作为函数输入
count_lines() {
wc -l
}
count_lines << EOF
line1
line2
line3
EOF
# 输出:3(行数)
AI写代码第四部分:编写健壮的脚本
1. set 命令
在脚本开头使用 set 命令可以使其更安全、更健壮。#!/bin/bash
set -euo pipefail
AI写代码
set -e: 脚本中任何命令失败(返回非零退出状态码)时,立即退出。set -u: 尝试使用未定义的变量时,立即退出。
set -o pipefail: 在管道中,只要有任何一个命令失败,整个管道的退出状态码就是失败的。
2. 解析脚本选项 (getopts)
用于解析传递给脚本的命令行选项(如 -f, -v)。#!/bin/bash
set -euo pipefail
VERBOSE=false
FILENAME=""
# f: 表示-f选项需要一个参数
# v 表示-v选项不需要参数
while getopts 'vf:' OPTION; do
case "$OPTION" in
v)
VERBOSE=true
;;
f)
FILENAME="$OPTARG"
;;
?)
echo "Usage: $(basename $0) [-v] [-f filename]"
exit 1
;;
esac
done
if [[ $VERBOSE == true ]]; then
echo "Verbose mode is ON."
fi
if [[ -n "$FILENAME" ]]; then
echo "Processing file: $FILENAME"
else
echo "No filename provided."
fi
AI写代码执行示例:
./myscript.sh -v -f data.txt
./myscript.sh -f report.csv
3. 信号陷阱 (trap)
允许你在脚本接收到特定信号(如 Ctrl+C)时执行一段代码,常用于清理临时文件。#!/bin/bash
set -euo pipefail
# 创建一个临时文件
TMP_FILE=$(mktemp)
echo "Created temporary file: $TMP_FILE"
# 定义清理函数
cleanup() {
echo "Caught signal! Cleaning up..."
rm -f "$TMP_FILE"
echo "Cleanup finished."
exit 1
}
# 设置陷阱:当接收到 INT(Ctrl+C) 或 TERM 信号时,执行 cleanup 函数
trap cleanup INT TERM
# 主逻辑
echo "Script is running, press Ctrl+C to test the trap."
sleep 60 # 模拟长时间运行的任务
echo "Script finished normally."
# 正常退出前也要清理
rm -f "$TMP_FILE"
AI写代码捕获系统信号(如 Ctrl+C 发送的 SIGINT),执行自定义操作。
#!/bin/bash
# 捕获 SIGINT 信号(Ctrl+C)
trap 'echo " 不要按 Ctrl+C!"; exit 1' SIGINT
echo "运行中(按 Ctrl+C 测试)..."
while true; do
sleep 1
done
AI写代码
4. 正则表达式与文本处理
Shell 结合 grep(搜索)、sed(编辑)、awk(分析)可强大处理文本。1. grep:文本搜索
语法:grep [选项] 模式 文件
常用选项:-i(忽略大小写)、-v(反向匹配)、-n(显示行号)、-E(扩展正则)。
#!/bin/bash
# 在文件中搜索包含 "error" 的行(区分大小写)
grep "error" log.txt
# 忽略大小写搜索,显示行号
grep -in "warning" log.txt
# 反向匹配(不包含 "debug" 的行)
grep -v "debug" log.txt
# 扩展正则(-E),匹配 "apple" 或 "banana"
grep -E "apple|banana" fruits.txt
AI写代码2. sed:文本替换与编辑
语法:sed [选项] '命令' 文件
常用命令:s/原字符串/新字符串/(替换,默认替换每行第一个匹配)、s/.../.../g(全局替换)。
#!/bin/bash
# 替换文件中 "old" 为 "new"(仅输出,不修改原文件)
sed 's/old/new/' text.txt
# 全局替换并修改原文件(-i 选项,备份用 -i.bak)
sed -i 's/hello/HELLO/g' greet.txt # 所有 hello 替换为 HELLO
# 删除空行(d 命令删除匹配行)
sed '/^$/d' input.txt # ^$ 匹配空行
AI写代码3. awk:文本分析与处理
擅长按列处理文本(默认空格分隔),语法:awk '模式 {动作}' 文件。#!/bin/bash
# 打印文件第2列和第4列
awk '{print $2, $4}' data.txt
# 按条件过滤(第3列数值 > 100 的行)
awk '$3 > 100 {print $0}' data.txt # $0 表示整行
# 自定义分隔符(-F 选项),按逗号分隔,打印第1列
awk -F ',' '{print $1}' csvfile.txt
AI写代码5. 进程管理
Shell 可启动、查看、终止进程。1. 后台运行与 jobs
&:在命令后加 & 使其后台运行。
jobs:查看当前终端的后台进程。
fg %n:将第 n 个后台进程调回前台。
bg %n:将暂停的后台进程继续运行。
#!/bin/bash
# 后台运行长时间任务(输出重定向到文件)
sleep 30 > sleep.log 2>&1 &
echo "后台进程 ID:$!" # $! 是后台进程 PID
# 查看后台进程
jobs
# 终止进程(kill)
pid=$!
kill $pid # 发送 SIGTERM 信号
# kill -9 $pid # 强制终止(SIGKILL,无法捕获)
AI写代码2. 进程替换(<(命令)、>(命令))
将命令输出作为临时文件,用于需要文件参数的命令。#!/bin/bash
# 比较两个命令的输出(无需临时文件)
diff <(ls dir1) <(ls dir2) # 比较 dir1 和 dir2 的文件列表
# 将输出同时发送到终端和文件(tee 命令)
ls -l | tee >(grep ".sh" > sh_files.txt) # 筛选 .sh 文件到 sh_files.txt,同时显示所有
AI写代码
6. 调试脚本
bash -n 脚本:检查语法错误(不执行)。
bash -x 脚本:执行并输出每一行命令(调试细节)。
在脚本中用 set -x(开启调试)、set +x(关闭)。
#!/bin/bash
set -x # 开启调试
a=5
b=3
echo $((a + b))
set +x # 关闭调试
echo "调试结束"
AI写代码
第五部分:综合脚本示例
案例 1:批量重命名文件
#!/bin/bash
# 功能:将当前目录所有 .txt 文件重命名为 "prefix_数字.txt"(如 prefix_1.txt)
count=1
for file in *.txt; do
# 跳过非文件(如目录)
if [ ! -f "$file" ]; then
continue
fi
# 重命名
mv "$file" "prefix_${count}.txt"
echo "重命名:$file → prefix_${count}.txt"
((count++))
done
AI写代码案例 2:系统监控脚本
#!/bin/bash
# 功能:监控系统 CPU、内存、磁盘使用率,超过阈值则报警
# 阈值(百分比)
CPU_THRESHOLD=80
MEM_THRESHOLD=80
DISK_THRESHOLD=90
# 获取 CPU 使用率(取整数)
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4}' | cut -d. -f1)
# 获取内存使用率
mem_usage=$(free | grep Mem | awk '{print $3/$2 * 100}' | cut -d. -f1)
# 获取磁盘使用率(根目录)
disk_usage=$(df -h / | grep / | awk '{print $5}' | sed 's/%//')
# 检查 CPU
if (( cpu_usage > CPU_THRESHOLD )); then
echo "报警:CPU 使用率过高($cpu_usage%)"
fi
# 检查内存
if (( mem_usage > MEM_THRESHOLD )); then
echo "报警:内存使用率过高($mem_usage%)"
fi
# 检查磁盘
if (( disk_usage > DISK_THRESHOLD )); then
echo "报警:磁盘使用率过高($disk_usage%)"
fi
AI写代码总结
掌握 Shell 脚本是一个循序渐进的过程。入门: 从 echo、变量、read 和简单的 if 开始,学会编写和执行基础脚本。
进阶: 熟练运用循环、函数、case 语句和各种条件判断,能够编写逻辑复杂的脚本。
高级: 掌握数组、bc 计算、颜色输出、进程替换、getopts 和 trap 等高级特性,编写出功能强大、交互友好且非常健壮的专业脚本。
————————————————
版权声明:本文为CSDN博主「zwjapple」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zwjapple/article/details/149243023