Shell高级编程技巧可以让我们编写更强大、更灵活的脚本。本章将介绍高级变量操作、高级函数编程、进程替换、信号处理等高级主题,帮助您成为Shell编程专家。
变量替换语法:
| 语法 | 说明 | 示例 |
|---|---|---|
${var} |
变量值 | ${name} |
${#var} |
变量长度 | ${#name} |
${var:-word} |
变量为空时返回word | ${name:-"default"} |
${var:=word} |
变量为空时赋值word | ${name:="default"} |
${var:+word} |
变量非空时返回word | ${name:+"exists"} |
${var:?message} |
变量为空时报错 | ${name:?"name is empty"} |
示例:
#!/bin/bash
# 变量长度
name="东巴文"
echo "变量长度: ${#name}" # 输出: 3
# 默认值
unset var
echo "${var:-default}" # 输出: default
echo "$var" # 输出: (空)
# 赋默认值
unset var
echo "${var:=default}" # 输出: default
echo "$var" # 输出: default
# 变量存在时返回值
var="value"
echo "${var:+exists}" # 输出: exists
# 变量为空时报错
unset var
echo "${var:?变量未设置}" # 输出: bash: var: 变量未设置
东巴文理解:变量替换可以简化条件判断,提高代码可读性。
字符串操作语法:
| 语法 | 说明 | 示例 |
|---|---|---|
${var#pattern} |
删除开头最短匹配 | ${path#*/} |
${var##pattern} |
删除开头最长匹配 | ${path##*/} |
${var%pattern} |
删除结尾最短匹配 | ${path%/*} |
${var%%pattern} |
删除结尾最长匹配 | ${path%%/*} |
${var:offset} |
从offset开始截取 | ${str:3} |
${var:offset:length} |
从offset截取length个字符 | ${str:3:5} |
${var/pattern/replace} |
替换第一个匹配 | ${str/old/new} |
${var//pattern/replace} |
替换所有匹配 | ${str//old/new} |
${var/#pattern/replace} |
替换开头匹配 | ${str/#old/new} |
${var/%pattern/replace} |
替换结尾匹配 | ${str/%old/new} |
示例:
#!/bin/bash
path="/usr/local/bin/python"
# 删除开头最短匹配
echo "${path#*/}" # 输出: usr/local/bin/python
# 删除开头最长匹配
echo "${path##*/}" # 输出: python
# 删除结尾最短匹配
echo "${path%/*}" # 输出: /usr/local/bin
# 删除结尾最长匹配
echo "${path%%/*}" # 输出: (空)
# 字符串截取
str="Hello World"
echo "${str:6}" # 输出: World
echo "${str:0:5}" # 输出: Hello
# 字符串替换
str="hello world world"
echo "${str/world/WORLD}" # 输出: hello WORLD world
echo "${str//world/WORLD}" # 输出: hello WORLD WORLD
echo "${str/#hello/HELLO}" # 输出: HELLO world world
echo "${str/%world/WORLD}" # 输出: hello world WORLD
# 大小写转换
str="Hello World"
echo "${str,,}" # 输出: hello world (转小写)
echo "${str^^}" # 输出: HELLO WORLD (转大写)
echo "${str,}" # 输出: hello World (首字母小写)
echo "${str^}" # 输出: Hello World (首字母大写)
使用${!var}语法:
#!/bin/bash
# 间接引用
name="value"
ref="name"
echo "${!ref}" # 输出: value
# 等价于
eval echo \$$ref # 输出: value
# 动态变量名
for i in 1 2 3; do
var_$i="value_$i"
done
echo "${var_1}" # 输出: value_1
echo "${var_2}" # 输出: value_2
echo "${var_3}" # 输出: value_3
# 使用间接引用访问动态变量
for i in 1 2 3; do
ref="var_$i"
echo "${!ref}"
done
#!/bin/bash
# 数组间接引用
arr=("one" "two" "three")
ref="arr[@]"
echo "${!ref}" # 输出: one two three
# 动态数组
for i in 1 2 3; do
declare -a "arr_$i=(a b c)"
done
# 访问动态数组
ref="arr_1[@]"
echo "${!ref}" # 输出: a b c
# 使用eval
eval echo \${arr_2[@]} # 输出: a b c
东巴文提示:间接引用可以实现动态变量名,但要注意安全性。
declare选项:
| 选项 | 说明 |
|---|---|
-a |
声明数组 |
-A |
声明关联数组 |
-i |
声明整数 |
-r |
声明只读变量 |
-x |
声明环境变量 |
-u |
转换为大写 |
-l |
转换为小写 |
-n |
声明名称引用 |
示例:
#!/bin/bash
# 声明整数
declare -i num=10
num="abc" # 自动转换为0
echo "$num" # 输出: 0
# 声明只读变量
declare -r PI=3.14159
PI=3.14 # 报错: bash: PI: 只读变量
# 声明数组
declare -a arr=(1 2 3)
echo "${arr[@]}" # 输出: 1 2 3
# 声明关联数组
declare -A user
user[name]="东巴文"
user[age]=25
echo "${user[name]}" # 输出: 东巴文
# 转换大小写
declare -u upper="hello"
echo "$upper" # 输出: HELLO
declare -l lower="WORLD"
echo "$lower" # 输出: world
# 名称引用
declare -n ref=user
echo "${ref[name]}" # 输出: 东巴文
#!/bin/bash
# 全局变量
global_var="全局变量"
function test_scope() {
# 局部变量
local local_var="局部变量"
echo "函数内访问全局变量: $global_var"
echo "函数内访问局部变量: $local_var"
# 修改全局变量
global_var="修改后的全局变量"
}
test_scope
echo "函数外访问全局变量: $global_var"
echo "函数外访问局部变量: $local_var" # 输出: (空)
函数库文件:
#!/bin/bash
#===============================================================================
# 文件名称:utils.lib
# 文件功能:通用函数库
# 作者:东巴文
#===============================================================================
# 颜色定义
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color
# 打印函数
function print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
function print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
function print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
function print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 日志函数
function log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" >> /var/log/script.log
}
# 验证函数
function is_root() {
[[ $EUID -eq 0 ]]
}
function is_installed() {
command -v "$1" &> /dev/null
}
function is_file() {
[[ -f "$1" ]]
}
function is_dir() {
[[ -d "$1" ]]
}
# 字符串函数
function trim() {
local var="$1"
var="${var#"${var%%[![:space:]]*}"}"
var="${var%"${var##*[![:space:]]}"}"
echo "$var"
}
function to_lower() {
echo "${1,,}"
}
function to_upper() {
echo "${1^^}"
}
# 数组函数
function array_contains() {
local needle="$1"
shift
local item
for item in "$@"; do
[[ "$item" == "$needle" ]] && return 0
done
return 1
}
function array_unique() {
local -A seen
local item
for item in "$@"; do
[[ -z "${seen[$item]}" ]] && echo "$item"
seen[$item]=1
done
}
# 数学函数
function max() {
local max=$1
shift
for num in "$@"; do
((num > max)) && max=$num
done
echo "$max"
}
function min() {
local min=$1
shift
for num in "$@"; do
((num < min)) && min=$num
done
echo "$min"
}
function sum() {
local total=0
for num in "$@"; do
((total += num))
done
echo "$total"
}
# 网络函数
function get_ip() {
hostname -I | awk '{print $1}'
}
function is_port_open() {
local host="$1"
local port="$2"
timeout 1 bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null
}
# 文件函数
function backup_file() {
local file="$1"
local backup="${file}.bak.$(date +%Y%m%d_%H%M%S)"
cp "$file" "$backup" && echo "$backup"
}
function get_file_size() {
local file="$1"
stat -c %s "$file" 2>/dev/null || stat -f %z "$file" 2>/dev/null
}
function get_file_md5() {
local file="$1"
md5sum "$file" 2>/dev/null | awk '{print $1}' || md5 "$file" 2>/dev/null | awk '{print $4}'
}
引入函数库:
#!/bin/bash
#===============================================================================
# 脚本名称:use_library.sh
# 脚本功能:使用函数库示例
# 作者:东巴文
#===============================================================================
# 引入函数库
LIB_PATH="/path/to/utils.lib"
if [[ ! -f "$LIB_PATH" ]]; then
echo "错误: 函数库不存在"
exit 1
fi
# source函数库
source "$LIB_PATH"
# 使用函数库中的函数
print_info "开始执行脚本"
# 检查是否为root用户
if is_root; then
print_warning "请勿使用root用户运行此脚本"
exit 1
fi
# 检查命令是否安装
if ! is_installed "git"; then
print_error "git未安装"
exit 1
fi
# 使用字符串函数
name=" 东巴文 "
trimmed=$(trim "$name")
print_info "去除空格: '$trimmed'"
# 使用数组函数
arr=(1 2 3 2 1)
unique=$(array_unique "${arr[@]}")
print_info "去重: $unique"
# 使用数学函数
numbers=(10 5 8 3 9)
print_info "最大值: $(max "${numbers[@]}")"
print_info "最小值: $(min "${numbers[@]}")"
print_info "总和: $(sum "${numbers[@]}")"
# 使用网络函数
ip=$(get_ip)
print_info "本机IP: $ip"
# 使用文件函数
file="/etc/passwd"
if is_file "$file"; then
size=$(get_file_size "$file")
print_info "文件大小: $size 字节"
md5=$(get_file_md5 "$file")
print_info "文件MD5: $md5"
fi
print_success "脚本执行完成"
东巴文提示:函数库可以提高代码复用性,减少重复代码。
递归示例:
#!/bin/bash
# 阶乘
function factorial() {
local n=$1
if ((n <= 1)); then
echo 1
else
local prev=$(factorial $((n - 1)))
echo $((n * prev))
fi
}
# 测试
echo "5! = $(factorial 5)" # 输出: 120
echo "10! = $(factorial 10)" # 输出: 3628800
# 斐波那契数列
function fibonacci() {
local n=$1
if ((n <= 0)); then
echo 0
elif ((n == 1)); then
echo 1
else
local a=$(fibonacci $((n - 1)))
local b=$(fibonacci $((n - 2)))
echo $((a + b))
fi
}
# 测试
for i in {0..10}; do
echo "F($i) = $(fibonacci $i)"
done
# 汉诺塔
function hanoi() {
local n=$1
local from=$2
local to=$3
local aux=$4
if ((n == 1)); then
echo "移动盘子 1 从 $from 到 $to"
else
hanoi $((n - 1)) "$from" "$aux" "$to"
echo "移动盘子 $n 从 $from 到 $to"
hanoi $((n - 1)) "$aux" "$to" "$from"
fi
}
# 测试
hanoi 3 "A" "C" "B"
#!/bin/bash
# 递归遍历目录
function traverse_dir() {
local dir="$1"
local indent="${2:-0}"
local prefix=$(printf "%${indent}s" "")
for item in "$dir"/*; do
if [[ -d "$item" ]]; then
echo "${prefix}📁 $(basename "$item")/"
traverse_dir "$item" $((indent + 2))
elif [[ -f "$item" ]]; then
echo "${prefix}📄 $(basename "$item")"
fi
done
}
# 测试
traverse_dir "/etc" 0
# 递归查找文件
function find_files() {
local dir="$1"
local pattern="$2"
for item in "$dir"/*; do
if [[ -d "$item" ]]; then
find_files "$item" "$pattern"
elif [[ -f "$item" ]]; then
if [[ "$(basename "$item")" == *"$pattern"* ]]; then
echo "$item"
fi
fi
done
}
# 测试
find_files "/etc" ".conf"
# 递归计算目录大小
function dir_size() {
local dir="$1"
local total=0
for item in "$dir"/*; do
if [[ -d "$item" ]]; then
total=$((total + $(dir_size "$item")))
elif [[ -f "$item" ]]; then
total=$((total + $(stat -c %s "$item")))
fi
done
echo "$total"
}
# 测试
size=$(dir_size "/var/log")
echo "目录大小: $((size / 1024)) KB"
#!/bin/bash
# 使用回调函数处理数组
function process_array() {
local callback="$1"
shift
local arr=("$@")
for item in "${arr[@]}"; do
$callback "$item"
done
}
# 回调函数
function print_item() {
echo "处理: $1"
}
function double_item() {
echo "$(( $1 * 2 ))"
}
function square_item() {
echo "$(( $1 * $1 ))"
}
# 测试
arr=(1 2 3 4 5)
echo "打印元素:"
process_array print_item "${arr[@]}"
echo -e "\n元素翻倍:"
process_array double_item "${arr[@]}"
echo -e "\n元素平方:"
process_array square_item "${arr[@]}"
# 使用回调函数过滤数组
function filter_array() {
local callback="$1"
shift
local arr=("$@")
local result=()
for item in "${arr[@]}"; do
if $callback "$item"; then
result+=("$item")
fi
done
echo "${result[@]}"
}
# 过滤函数
function is_even() {
local num=$1
(( num % 2 == 0 ))
}
function is_positive() {
local num=$1
(( num > 0 ))
}
# 测试
arr=(-2 -1 0 1 2 3 4 5)
echo "偶数: $(filter_array is_even "${arr[@]}")"
echo "正数: $(filter_array is_positive "${arr[@]}")"
#!/bin/bash
# 事件系统
declare -A event_handlers
# 注册事件处理器
function on() {
local event="$1"
local handler="$2"
event_handlers["$event"]="$handler"
}
# 触发事件
function emit() {
local event="$1"
shift
local args=("$@")
if [[ -n "${event_handlers[$event]}" ]]; then
${event_handlers[$event]} "${args[@]}"
fi
}
# 事件处理器
function on_start() {
echo "脚本开始执行"
}
function on_complete() {
echo "脚本执行完成"
}
function on_error() {
echo "脚本执行出错: $1"
}
# 注册事件
on "start" "on_start"
on "complete" "on_complete"
on "error" "on_error"
# 触发事件
emit "start"
# 执行任务
if ! ls /nonexistent &> /dev/null; then
emit "error" "目录不存在"
fi
emit "complete"
东巴文理解:回调函数和事件驱动可以实现灵活的程序设计。
进程替换语法:
| 语法 | 说明 |
|---|---|
<(command) |
进程输出替换为文件 |
>(command) |
进程输入替换为文件 |
示例:
#!/bin/bash
# 进程输出替换
# 将命令输出作为文件传递给另一个命令
diff <(ls /dir1) <(ls /dir2)
# 比较两个命令的输出
diff <(echo "a b c") <(echo "a b d")
# 将进程输出作为输入
cat <(echo "Hello") <(echo "World")
# 进程输入替换
# 将命令输入重定向到进程
echo "Hello World" > >(tee output.txt)
# 使用进程替换处理数据
sort <(grep "error" /var/log/syslog) | uniq -c
东巴文理解:进程替换将命令的输出/输入当作文件来处理,非常灵活。
#!/bin/bash
# 使用管道
ls /dir1 | sort > sorted1.txt
ls /dir2 | sort > sorted2.txt
diff sorted1.txt sorted2.txt
# 使用进程替换(更简洁)
diff <(ls /dir1 | sort) <(ls /dir2 | sort)
# 管道只能单向传递数据
# 进程替换可以多向传递数据
# 示例:同时处理多个数据源
join <(sort file1.txt) <(sort file2.txt)
# 示例:比较远程文件
diff <(ssh user@host1 cat /etc/passwd) <(ssh user@host2 cat /etc/passwd)
#!/bin/bash
# 比较两个目录的文件列表
echo "比较目录差异:"
diff -y <(ls -1 /dir1 | sort) <(ls -1 /dir2 | sort)
# 比较两个配置文件(去除注释和空行)
echo -e "\n比较配置文件:"
diff <(grep -v '^#' /etc/apache2/apache2.conf | grep -v '^$') \
<(grep -v '^#' /etc/apache2/apache2.conf.bak | grep -v '^$')
# 比较两个进程的环境变量
echo -e "\n比较进程环境变量:"
diff <(cat /proc/$PID1/environ | tr '\0' '\n' | sort) \
<(cat /proc/$PID2/environ | tr '\0' '\n' | sort)
# 比较两个数据库表
echo -e "\n比较数据库表:"
diff <(mysql -e "SELECT * FROM table1" db1 | sort) \
<(mysql -e "SELECT * FROM table2" db2 | sort)
#!/bin/bash
# 合并多个数据源
cat <(echo "=== 文件1 ===") \
<(cat file1.txt) \
<(echo -e "\n=== 文件2 ===") \
<(cat file2.txt) \
> merged.txt
# 使用join合并数据
join -1 1 -2 1 \
<(sort file1.txt) \
<(sort file2.txt) \
> joined.txt
# 使用paste合并数据
paste <(cut -d: -f1,3 /etc/passwd) \
<(cut -d: -f1,7 /etc/passwd) \
> combined.txt
# 合并多个日志文件并按时间排序
sort -k4 \
<(cat /var/log/nginx/access.log) \
<(cat /var/log/nginx/access.log.1) \
> merged_logs.txt
#!/bin/bash
# 处理多个数据源
awk 'NR==FNR{a[$1]=$2; next} {print $1, a[$1], $2}' \
<(cat file1.txt) \
<(cat file2.txt)
# 使用进程替换进行复杂处理
while read -r line; do
# 处理每一行
echo "处理: $line"
done < <(grep "error" /var/log/syslog | sort | uniq)
# 并行处理多个文件
for file in *.txt; do
process_file < <(cat "$file") > "${file}.processed" &
done
wait
# 使用进程替换进行数据验证
comm -23 \
<(sort required_packages.txt) \
<(dpkg -l | awk '{print $2}' | sort) \
> missing_packages.txt
常用信号列表:
| 信号 | 编号 | 说明 | 默认行为 |
|---|---|---|---|
SIGHUP |
1 | 挂起 | 终止进程 |
SIGINT |
2 | 中断(Ctrl+C) | 终止进程 |
SIGQUIT |
3 | 退出(Ctrl+\) | 终止进程并转储 |
SIGKILL |
9 | 强制终止 | 终止进程 |
SIGTERM |
15 | 正常终止 | 终止进程 |
SIGCONT |
18 | 继续 | 恢复进程 |
SIGSTOP |
19 | 停止 | 停止进程 |
SIGTSTP |
20 | 终端停止(Ctrl+Z) | 停止进程 |
查看信号列表:
# 查看所有信号
kill -l
# 查看信号编号
kill -l SIGTERM # 输出: 15
kill -l 15 # 输出: TERM
发送信号:
# 发送信号给进程
kill -SIGTERM 1234
kill -15 1234
kill 1234 # 默认发送SIGTERM
# 强制终止进程
kill -SIGKILL 1234
kill -9 1234
# 发送信号给进程组
kill -SIGTERM -1234 # 负数表示进程组
# 使用pkill发送信号
pkill -SIGTERM nginx
pkill -f "nginx.*worker"
# 使用killall发送信号
killall -SIGTERM nginx
东巴文理解:信号是进程间通信的一种方式,可以控制进程的行为。
trap语法:
trap '命令' 信号列表
示例:
#!/bin/bash
# 捕获SIGINT信号(Ctrl+C)
trap 'echo "捕获到SIGINT信号"; exit 1' SIGINT
# 捕获多个信号
trap 'echo "捕获到信号"; exit 1' SIGINT SIGTERM SIGHUP
# 忽略信号
trap '' SIGINT # 忽略Ctrl+C
# 恢复信号默认行为
trap - SIGINT
# 捕获EXIT信号(脚本退出时执行)
trap 'echo "脚本退出"; rm -f /tmp/tempfile' EXIT
# 创建临时文件
temp_file=$(mktemp)
echo "临时文件: $temp_file"
# 脚本结束时自动删除临时文件
trap 'rm -f "$temp_file"' EXIT
# 使用函数处理信号
function cleanup() {
echo "清理资源..."
rm -f /tmp/tempfile
exit 1
}
trap cleanup SIGINT SIGTERM
守护脚本:
#!/bin/bash
#===============================================================================
# 脚本名称:daemon.sh
# 脚本功能:守护进程脚本
# 作者:东巴文
#===============================================================================
PID_FILE="/var/run/daemon.pid"
LOG_FILE="/var/log/daemon.log"
# 清理函数
function cleanup() {
echo "$(date): 守护进程停止" >> "$LOG_FILE"
rm -f "$PID_FILE"
exit 0
}
# 捕获信号
trap cleanup SIGTERM SIGINT SIGHUP
# 检查是否已经在运行
if [[ -f "$PID_FILE" ]]; then
pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
echo "守护进程已经在运行 (PID: $pid)"
exit 1
else
rm -f "$PID_FILE"
fi
fi
# 写入PID
echo $$ > "$PID_FILE"
echo "$(date): 守护进程启动 (PID: $$)" >> "$LOG_FILE"
# 主循环
while true; do
# 执行任务
echo "$(date): 执行任务..." >> "$LOG_FILE"
# 检查某个服务是否运行
if ! systemctl is-active --quiet nginx; then
echo "$(date): nginx未运行,尝试启动" >> "$LOG_FILE"
systemctl start nginx
fi
# 等待
sleep 60
done
优雅退出脚本:
#!/bin/bash
#===============================================================================
# 脚本名称:graceful_exit.sh
# 脚本功能:优雅退出脚本
# 作者:东巴文
#===============================================================================
# 临时文件数组
declare -a temp_files
# 清理函数
function cleanup() {
echo -e "\n正在清理资源..."
# 删除临时文件
for file in "${temp_files[@]}"; do
if [[ -f "$file" ]]; then
echo "删除临时文件: $file"
rm -f "$file"
fi
done
# 关闭数据库连接
if [[ -n "$db_conn" ]]; then
echo "关闭数据库连接"
# 关闭连接的代码
fi
# 停止后台进程
if [[ -n "$bg_pid" ]]; then
echo "停止后台进程: $bg_pid"
kill "$bg_pid" 2>/dev/null
fi
echo "清理完成"
exit 1
}
# 捕获信号
trap cleanup SIGINT SIGTERM SIGHUP
# 创建临时文件
temp_file1=$(mktemp)
temp_files+=("$temp_file1")
echo "创建临时文件: $temp_file1"
temp_file2=$(mktemp)
temp_files+=("$temp_file2")
echo "创建临时文件: $temp_file2"
# 启动后台进程
sleep 1000 &
bg_pid=$!
echo "启动后台进程: $bg_pid"
# 主循环
echo "脚本运行中,按Ctrl+C退出"
while true; do
echo "处理中..."
sleep 2
done
#!/bin/bash
#===============================================================================
# 脚本名称:process_control.sh
# 脚本功能:进程控制脚本
# 作者:东巴文
#===============================================================================
# 启动进程
function start_process() {
local name="$1"
local command="$2"
# 检查是否已经在运行
if pgrep -f "$name" &>/dev/null; then
echo "进程 $name 已经在运行"
return 1
fi
# 启动进程
nohup $command >/dev/null 2>&1 &
local pid=$!
# 等待进程启动
sleep 1
if kill -0 "$pid" 2>/dev/null; then
echo "进程 $name 启动成功 (PID: $pid)"
return 0
else
echo "进程 $name 启动失败"
return 1
fi
}
# 停止进程
function stop_process() {
local name="$1"
local timeout="${2:-10}"
# 获取进程PID
local pid=$(pgrep -f "$name")
if [[ -z "$pid" ]]; then
echo "进程 $name 未运行"
return 1
fi
# 发送SIGTERM信号
kill -TERM "$pid"
# 等待进程退出
local count=0
while kill -0 "$pid" 2>/dev/null; do
sleep 1
((count++))
if ((count >= timeout)); then
echo "进程 $name 未响应,强制终止"
kill -KILL "$pid"
break
fi
done
echo "进程 $name 已停止"
return 0
}
# 重启进程
function restart_process() {
local name="$1"
local command="$2"
stop_process "$name"
sleep 2
start_process "$name" "$command"
}
# 查看进程状态
function status_process() {
local name="$1"
if pgrep -f "$name" &>/dev/null; then
echo "进程 $name 正在运行"
pgrep -af "$name"
return 0
else
echo "进程 $name 未运行"
return 1
fi
}
# 主菜单
case "$1" in
start)
start_process "$2" "$3"
;;
stop)
stop_process "$2"
;;
restart)
restart_process "$2" "$3"
;;
status)
status_process "$2"
;;
*)
echo "用法: $0 {start|stop|restart|status} 进程名 [命令]"
exit 1
;;
esac
#!/bin/bash
#===============================================================================
# 脚本名称:timeout_control.sh
# 脚本功能:超时控制脚本
# 作者:东巴文
#===============================================================================
# 超时执行命令
function timeout_command() {
local timeout="$1"
shift
local command=("$@")
# 执行命令
"${command[@]}" &
local pid=$!
# 等待命令完成或超时
local count=0
while kill -0 "$pid" 2>/dev/null; do
sleep 1
((count++))
if ((count >= timeout)); then
echo "命令超时,终止进程"
kill -TERM "$pid"
sleep 1
# 如果进程还在运行,强制终止
if kill -0 "$pid" 2>/dev/null; then
kill -KILL "$pid"
fi
return 124 # 超时退出码
fi
done
# 获取命令退出码
wait "$pid"
return $?
}
# 测试
echo "测试1: 正常命令"
timeout_command 5 sleep 2
echo "退出码: $?"
echo -e "\n测试2: 超时命令"
timeout_command 3 sleep 10
echo "退出码: $?"
# 使用timeout命令(更简单)
echo -e "\n测试3: 使用timeout命令"
timeout 3 sleep 10
echo "退出码: $?"
避免不必要的管道:
#!/bin/bash
# 不推荐:使用多个管道
result=$(cat file.txt | grep "pattern" | awk '{print $1}' | sort | uniq)
# 推荐:使用单个命令
result=$(awk '/pattern/ {print $1}' file.txt | sort -u)
# 不推荐:使用cat
result=$(cat file.txt | grep "pattern")
# 推荐:直接读取文件
result=$(grep "pattern" file.txt)
# 不推荐:使用多个命令
lines=$(wc -l < file.txt)
words=$(wc -w < file.txt)
chars=$(wc -c < file.txt)
# 推荐:使用单个命令
read lines words chars <<< $(wc file.txt)
使用Shell内置功能:
#!/bin/bash
# 不推荐:使用外部命令
basename=$(basename "$path")
dirname=$(dirname "$path")
# 推荐:使用参数扩展
basename="${path##*/}"
dirname="${path%/*}"
# 不推荐:使用expr
result=$(expr $a + $b)
# 推荐:使用算术扩展
result=$((a + b))
# 不推荐:使用sed
result=$(echo "$str" | sed 's/old/new/')
# 推荐:使用参数扩展
result="${str/old/new}"
# 不推荐:使用tr
result=$(echo "$str" | tr '[:lower:]' '[:upper:]')
# 推荐:使用参数扩展
result="${str^^}"
# 不推荐:使用外部命令
length=$(echo -n "$str" | wc -c)
# 推荐:使用参数扩展
length=${#str}
东巴文提示:使用Shell内置功能可以避免创建子进程,提高性能。
#!/bin/bash
# 串行处理(慢)
for file in *.txt; do
process_file "$file"
done
# 并行处理(快)
for file in *.txt; do
process_file "$file" &
done
wait
# 限制并发数
max_jobs=4
count=0
for file in *.txt; do
process_file "$file" &
((count++))
if ((count >= max_jobs)); then
wait -n # 等待任意一个后台任务完成
((count--))
fi
done
wait
# 使用xargs并行处理
find . -name "*.txt" -print0 | xargs -0 -P 4 -I {} process_file "{}"
# 使用GNU parallel
find . -name "*.txt" | parallel -j 4 process_file {}
#!/bin/bash
#===============================================================================
# 脚本名称:process_pool.sh
# 脚本功能:进程池实现
# 作者:东巴文
#===============================================================================
# 进程池大小
POOL_SIZE=4
# 任务队列
declare -a task_queue
# 添加任务
function add_task() {
task_queue+=("$1")
}
# 执行任务
function execute_task() {
local task="$1"
echo "执行任务: $task"
# 模拟耗时操作
sleep 2
echo "完成任务: $task"
}
# 进程池
function process_pool() {
local running=0
for task in "${task_queue[@]}"; do
# 等待空闲槽位
while ((running >= POOL_SIZE)); do
wait -n
((running--))
done
# 执行任务
execute_task "$task" &
((running++))
done
# 等待所有任务完成
wait
}
# 添加任务
for i in {1..10}; do
add_task "任务$i"
done
# 执行
echo "开始处理任务..."
start_time=$(date +%s)
process_pool
end_time=$(date +%s)
echo "所有任务完成,耗时: $((end_time - start_time)) 秒"
#!/bin/bash
# 不推荐:读取整个文件到内存
content=$(cat large_file.txt)
# 推荐:逐行处理
while IFS= read -r line; do
process_line "$line"
done < large_file.txt
# 不推荐:使用数组存储大量数据
declare -a large_array
while IFS= read -r line; do
large_array+=("$line")
done < large_file.txt
# 推荐:使用文件或数据库存储
while IFS= read -r line; do
echo "$line" >> temp_file.txt
done < large_file.txt
# 不推荐:使用大量变量
var1="value1"
var2="value2"
# ... 100个变量
# 推荐:使用数组
declare -a vars=("value1" "value2" ...)
#!/bin/bash
# 及时关闭文件描述符
exec 3< file.txt
while IFS= read -r -u 3 line; do
process_line "$line"
done
exec 3<&- # 关闭文件描述符
# 及时删除临时文件
temp_file=$(mktemp)
trap 'rm -f "$temp_file"' EXIT
# 使用临时文件
echo "data" > "$temp_file"
process_file "$temp_file"
# 及时清空大变量
large_data=$(cat large_file.txt)
process_data "$large_data"
unset large_data # 释放内存
# 使用函数限制变量作用域
function process_chunk() {
local data="$1"
# 处理数据
# 函数结束后,局部变量自动释放
}
process_chunk "$chunk"
Shell调试选项:
| 选项 | 说明 |
|---|---|
-n |
只读取脚本,不执行(检查语法) |
-v |
显示读取的每一行 |
-x |
显示执行的每一行 |
-e |
命令出错时立即退出 |
-u |
使用未定义变量时报错 |
-o pipefail |
管道中任何命令出错都返回错误 |
示例:
#!/bin/bash
# 检查语法
bash -n script.sh
# 显示执行的每一行
bash -x script.sh
# 显示读取的每一行
bash -v script.sh
# 组合使用
bash -xv script.sh
# 在脚本中启用调试
#!/bin/bash
set -x # 启用调试
# ... 代码 ...
set +x # 关闭调试
# 严格模式
#!/bin/bash
set -euo pipefail
#!/bin/bash
# 调试函数
function debug() {
[[ $DEBUG -eq 1 ]] && echo "[DEBUG] $*" >&2
}
# 使用调试函数
DEBUG=1
function process_file() {
local file="$1"
debug "处理文件: $file"
if [[ ! -f "$file" ]]; then
debug "文件不存在: $file"
return 1
fi
debug "文件大小: $(stat -c %s "$file")"
# ... 处理文件 ...
}
process_file "/etc/passwd"
# 跟踪函数调用
function trace() {
echo "[TRACE] ${FUNCNAME[1]} called with: $*" >&2
}
function my_function() {
trace "$@"
# ... 函数代码 ...
}
#!/bin/bash
# 严格模式
set -euo pipefail
# 错误处理函数
function error_handler() {
local line_no=$1
local error_code=$?
echo "错误发生在第 $line_no 行"
echo "错误代码: $error_code"
# 清理资源
cleanup
exit $error_code
}
# 设置错误处理
trap 'error_handler $LINENO' ERR
# 清理函数
function cleanup() {
echo "清理资源..."
rm -f /tmp/tempfile
}
# 测试
echo "开始执行"
false # 这会触发错误
echo "这行不会执行"
#!/bin/bash
#===============================================================================
# 脚本名称:logging.sh
# 脚本功能:日志记录脚本
# 作者:东巴文
#===============================================================================
# 日志文件
LOG_FILE="/var/log/script.log"
# 日志函数
function log() {
local level="$1"
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
function log_info() {
log "INFO" "$@"
}
function log_warning() {
log "WARNING" "$@"
}
function log_error() {
log "ERROR" "$@"
}
function log_debug() {
[[ $DEBUG -eq 1 ]] && log "DEBUG" "$@"
}
# 使用日志
log_info "脚本开始执行"
log_debug "调试信息"
log_warning "警告信息"
log_error "错误信息"
# 带错误处理的日志
function safe_command() {
local description="$1"
shift
local command=("$@")
log_info "执行: $description"
if "${command[@]}" 2>&1 | tee -a "$LOG_FILE"; then
log_info "成功: $description"
return 0
else
log_error "失败: $description"
return 1
fi
}
# 使用
safe_command "创建目录" mkdir -p /path/to/dir
safe_command "复制文件" cp source.txt dest.txt
✅ 掌握高级变量操作 ✅ 学会函数库和递归 ✅ 理解进程替换技术 ✅ 掌握信号处理机制 ✅ 学会性能优化技巧 ✅ 掌握调试方法
完成本章学习后,请确认您能够:
东巴文(db-w.cn) - 让Linux学习更简单