主题
Shell脚本进阶
1. Shell脚本基础回顾
1.1 Shell脚本的概念
Shell脚本是一种用于自动化执行命令的脚本文件,它使用Shell语言编写,可以在命令行环境中执行。Shell脚本可以:
- 自动化执行一系列命令
- 处理文本数据
- 控制系统服务
- 进行系统管理任务
- 实现复杂的业务逻辑
1.2 常见的Shell类型
| Shell类型 | 描述 | 特点 | 默认系统 |
|---|---|---|---|
| bash | Bourne Again Shell | 功能丰富,支持数组、函数等高级特性 | Linux默认 |
| sh | Bourne Shell | 基础Shell,兼容性好 | 早期UNIX |
| csh | C Shell | 语法类似C语言 | 某些UNIX系统 |
| tcsh | Turbo C Shell | csh的增强版本 | 某些UNIX系统 |
| ksh | Korn Shell | 结合了bash和csh的优点 | 某些UNIX系统 |
| zsh | Z Shell | 功能强大,支持主题和插件 | 可自定义安装 |
1.3 Shell脚本的基本结构
bash
#!/bin/bash
# 脚本注释
# 变量定义
VARIABLE="value"
# 函数定义
function_name() {
echo "Function body"
}
# 主逻辑
if [ condition ]; then
echo "True"
else
echo "False"
fi
# 调用函数
function_name2. Shell脚本高级语法
2.1 变量高级用法
2.1.1 变量替换
bash
# 基本变量替换
name="John"
echo "Hello, $name"
# 变量替换与命令替换
echo "Current directory: $(pwd)"
echo "Current directory: `pwd`"
# 变量替换与字符串操作
string="Hello, World"
echo "Length: ${#string}" # 字符串长度
echo "Substring: ${string:7:5}" # 从位置7开始,取5个字符
echo "Replace: ${string/World/Shell}" # 替换字符串
echo "Remove prefix: ${string#Hello, }" # 移除前缀
echo "Remove suffix: ${string%, World}" # 移除后缀
# 变量默认值
unset var
echo "${var:-default}" # 如果var未设置,使用default
echo "${var:=default}" # 如果var未设置,设置为default并使用
echo "${var:+value}" # 如果var已设置,使用value
echo "${var:?error}" # 如果var未设置,输出error并退出2.1.2 数组
bash
# 数组定义
array=(
"element1"
"element2"
"element3"
)
# 访问数组元素
echo "First element: ${array[0]}"
echo "Second element: ${array[1]}"
echo "All elements: ${array[@]}"
echo "Number of elements: ${#array[@]}"
# 数组操作
array[3]="element4" # 添加元素
unset array[1] # 删除元素
array=(${array[@]}) # 重建数组(压缩索引)
# 遍历数组
for element in "${array[@]}"; do
echo "Element: $element"
done
# 关联数组(bash 4+)
declare -A assoc_array
assoc_array["name"]="John"
assoc_array["age"]="30"
assoc_array["city"]="New York"
# 访问关联数组
echo "Name: ${assoc_array[name]}"
echo "All keys: ${!assoc_array[@]}"
echo "All values: ${assoc_array[@]}"
# 遍历关联数组
for key in "${!assoc_array[@]}"; do
echo "$key: ${assoc_array[$key]}"
done2.2 函数高级用法
2.2.1 函数定义与调用
bash
# 函数定义方式 1
function hello {
echo "Hello, $1"
}
# 函数定义方式 2
hello() {
echo "Hello, $1"
}
# 函数调用
hello "World"
# 带返回值的函数
sum() {
local a=$1
local b=$2
return $((a + b))
}
sum 5 3
result=$?
echo "Sum: $result"
# 返回字符串的函数
get_greeting() {
local name=$1
echo "Hello, $name"
}
greeting=$(get_greeting "John")
echo "Greeting: $greeting"2.2.2 函数参数处理
bash
# 处理任意数量的参数
print_args() {
echo "Number of arguments: $#"
echo "All arguments: $@"
echo "First argument: $1"
echo "Second argument: $2"
}
print_args 1 2 3 4 5
# 移动参数
shift_args() {
echo "Initial arguments: $@"
shift 2 # 移除前2个参数
echo "After shift: $@"
}
shift_args a b c d e
# 遍历所有参数
for_each() {
for arg in "$@"; do
echo "Argument: $arg"
done
}
for_each apple banana cherry2.2.3 函数作用域
bash
# 全局变量
GLOBAL_VAR="global"
# 函数中的局部变量
scope_demo() {
local LOCAL_VAR="local"
GLOBAL_VAR="modified global"
echo "Inside function - LOCAL_VAR: $LOCAL_VAR"
echo "Inside function - GLOBAL_VAR: $GLOBAL_VAR"
}
# 调用函数
scope_demo
# 外部访问
echo "Outside function - GLOBAL_VAR: $GLOBAL_VAR"
echo "Outside function - LOCAL_VAR: $LOCAL_VAR" # 未定义2.3 控制结构高级用法
2.3.1 条件判断高级用法
bash
# 文件测试
test_file() {
local file=$1
if [ -f "$file" ]; then
echo "$file is a regular file"
elif [ -d "$file" ]; then
echo "$file is a directory"
elif [ -L "$file" ]; then
echo "$file is a symlink"
elif [ -e "$file" ]; then
echo "$file exists"
else
echo "$file does not exist"
fi
}
test_file "/etc/passwd"
test_file "/etc"
test_file "/etc/hosts"
test_file "/nonexistent"
# 字符串测试
string_test() {
local str1=$1
local str2=$2
if [ -z "$str1" ]; then
echo "str1 is empty"
elif [ -n "$str1" ]; then
echo "str1 is not empty"
fi
if [ "$str1" = "$str2" ]; then
echo "str1 equals str2"
elif [ "$str1" != "$str2" ]; then
echo "str1 does not equal str2"
fi
if [ "$str1" \< "$str2" ]; then
echo "str1 is less than str2"
elif [ "$str1" \> "$str2" ]; then
echo "str1 is greater than str2"
fi
}
string_test "apple" "banana"
# 数值测试
number_test() {
local num1=$1
local num2=$2
if [ $num1 -eq $num2 ]; then
echo "num1 equals num2"
elif [ $num1 -ne $num2 ]; then
echo "num1 does not equal num2"
fi
if [ $num1 -lt $num2 ]; then
echo "num1 is less than num2"
elif [ $num1 -le $num2 ]; then
echo "num1 is less than or equal to num2"
elif [ $num1 -gt $num2 ]; then
echo "num1 is greater than num2"
elif [ $num1 -ge $num2 ]; then
echo "num1 is greater than or equal to num2"
fi
}
number_test 5 10
# 逻辑运算
logic_test() {
local a=$1
local b=$2
if [ $a -gt 0 ] && [ $b -gt 0 ]; then
echo "Both numbers are positive"
fi
if [ $a -gt 0 ] || [ $b -gt 0 ]; then
echo "At least one number is positive"
fi
if ! [ $a -eq 0 ]; then
echo "a is not zero"
fi
}
logic_test 5 -3
# 高级条件表达式(bash)
advanced_cond() {
local num=$1
if [[ $num =~ ^[0-9]+$ ]]; then
echo "$num is a number"
else
echo "$num is not a number"
fi
local str=$2
if [[ $str == *"test"* ]]; then
echo "$str contains 'test'"
else
echo "$str does not contain 'test'"
fi
}
advanced_cond 42 "testing"2.3.2 循环高级用法
bash
# for 循环遍历文件
for file in *.txt; do
if [ -f "$file" ]; then
echo "Processing $file"
fi
done
# for 循环遍历数字
for i in {1..10}; do
echo "Number: $i"
done
# for 循环自定义步长
for i in {1..10..2}; do
echo "Odd number: $i"
done
# while 循环读取文件
while IFS= read -r line; do
echo "Line: $line"
done < "file.txt"
# while 循环条件判断
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
((count++))
done
# until 循环
count=1
until [ $count -gt 5 ]; do
echo "Count: $count"
((count++))
done
# 循环控制
for i in {1..10}; do
if [ $i -eq 5 ]; then
echo "Breaking at $i"
break
fi
if [ $i -eq 3 ]; then
echo "Skipping $i"
continue
fi
echo "Number: $i"
done
# select 循环(交互式菜单)
echo "Select an option:"
select option in "Option 1" "Option 2" "Option 3" "Exit"; do
case $option in
"Option 1")
echo "You selected Option 1"
;;
"Option 2")
echo "You selected Option 2"
;;
"Option 3")
echo "You selected Option 3"
;;
"Exit")
echo "Exiting..."
break
;;
*)
echo "Invalid option"
;;
esac
done2.3.3 案例语句高级用法
bash
# 基本 case 语句
case $1 in
start)
echo "Starting service"
;;
stop)
echo "Stopping service"
;;
restart)
echo "Restarting service"
;;
status)
echo "Checking status"
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
# 模式匹配
file_type() {
local file=$1
case $file in
*.txt)
echo "Text file"
;;
*.sh)
echo "Shell script"
;;
*.py)
echo "Python script"
;;
*.{jpg,jpeg,png,gif})
echo "Image file"
;;
*)
echo "Unknown file type"
;;
esac
}
file_type "example.txt"
file_type "script.sh"
file_type "image.jpg"
# 正则表达式匹配(bash)
regex_test() {
local input=$1
case $input in
[0-9]+)
echo "Number"
;;
[a-zA-Z]+)
echo "Letters only"
;;
[a-zA-Z0-9]+)
echo "Alphanumeric"
;;
*)
echo "Other"
;;
esac
}
regex_test "123"
regex_test "abc"
regex_test "abc123"
regex_test "abc-123"3. Shell脚本调试技巧
3.1 调试模式
bash
# 调试模式执行脚本
bash -x script.sh
# 在脚本中设置调试模式
#!/bin/bash
set -x # 开启调试模式
# 脚本内容
set +x # 关闭调试模式
# 详细调试模式
bash -v script.sh # 显示脚本内容后执行
bash -n script.sh # 检查语法错误,不执行
# 调试选项
set -e # 遇到错误立即退出
set -u # 遇到未定义变量立即退出
set -o pipefail # 管道命令失败时退出
set -x # 显示执行的命令
# 组合选项
set -euo pipefail3.2 日志记录
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
}
log_debug() {
if [ "$DEBUG" = "true" ]; then
echo "[DEBUG] $(date '+%Y-%m-%d %H:%M:%S') - $1"
fi
}
# 使用日志函数
DEBUG=true
log_info "Starting script"
log_debug "Debug information"
log_error "An error occurred"
# 日志重定向
exec > >(tee -a script.log) 2>&1 # 同时输出到终端和日志文件
# 带时间戳的日志
exec > >(while read line; do echo "$(date '+%Y-%m-%d %H:%M:%S') $line"; done >> script.log) 2>&13.3 错误处理
bash
# 错误处理函数
error_exit() {
local error_message="${1:-"Unknown error"}"
echo "Error: $error_message" >&2
exit 1
}
# 使用错误处理
if [ ! -f "file.txt" ]; then
error_exit "File not found"
fi
# 陷阱处理
trap 'error_exit "Script interrupted"' INT TERM
trap 'echo "Cleaning up..."; rm -f temp.txt' EXIT
# 命令执行状态检查
command || error_exit "Command failed"
# 安全执行
set -euo pipefail
# 恢复执行状态
set +e
command
status=$?
set -e
if [ $status -ne 0 ]; then
error_exit "Command failed with status $status"
fi4. Shell脚本的高级特性
4.1 进程管理
bash
# 后台执行
command &
# 等待后台进程
wait $!
# 进程ID
echo "Current PID: $$"
echo "Parent PID: $PPID"
# 进程状态检查
if ps -p $PID > /dev/null; then
echo "Process is running"
else
echo "Process is not running"
fi
# 杀死进程
kill $PID
kill -9 $PID # 强制杀死
# 进程优先级
nice -n 10 command # 低优先级
renice 10 -p $PID # 调整优先级
# 后台运行脚本
nohup script.sh > script.out 2>&1 &
# 定时执行
echo "command" | at now + 1 minute
# 周期性执行
watch -n 5 command # 每5秒执行一次4.2 信号处理
bash
# 信号处理函数
handle_signal() {
local signal=$1
echo "Received signal $signal"
case $signal in
SIGINT)
echo "Interrupted by user"
;;
SIGTERM)
echo "Termination request"
;;
SIGUSR1)
echo "User-defined signal 1"
;;
SIGUSR2)
echo "User-defined signal 2"
;;
esac
}
# 设置信号陷阱
trap 'handle_signal SIGINT' INT
trap 'handle_signal SIGTERM' TERM
trap 'handle_signal SIGUSR1' USR1
trap 'handle_signal SIGUSR2' USR2
# 发送信号
kill -USR1 $$
kill -USR2 $$
# 清理陷阱
trap - INT TERM USR1 USR2
# 退出陷阱
trap 'echo "Exiting..."; cleanup' EXIT
# 错误陷阱
trap 'echo "Error occurred at line $LINENO"; exit 1' ERR4.3 输入输出重定向
bash
# 标准输出重定向
command > output.txt # 覆盖输出
command >> output.txt # 追加输出
# 标准错误重定向
command 2> error.txt # 错误输出到文件
command 2>&1 # 错误重定向到标准输出
command > output.txt 2>&1 # 所有输出到文件
command &> output.txt # 所有输出到文件(bash)
# 输入重定向
command < input.txt # 从文件读取输入
command << EOF # Here文档
Line 1
Line 2
EOF
command <<< "Hello" # Here字符串(bash)
# 管道
command1 | command2 # 管道连接
command1 | tee output.txt # tee 命令
command1 | xargs command2 # xargs 命令
# 进程替换
diff <(command1) <(command2) # 比较两个命令的输出
# 文件描述符
exec 3> output.txt # 创建文件描述符3
echo "Hello" >&3 # 写入文件描述符3
exec 3>&- # 关闭文件描述符3
# 复制文件描述符
exec 3>&1 # 保存标准输出到文件描述符3
echo "To stdout" # 输出到标准输出
echo "To file" >&3 # 输出到保存的标准输出
exec 3>&- # 关闭文件描述符34.4 命令替换与扩展
bash
# 命令替换
current_dir=$(pwd)
echo "Current directory: $current_dir"
date=$(date '+%Y-%m-%d')
echo "Today is $date"
# 算术扩展
result=$((1 + 2 * 3))
echo "Result: $result"
# 变量扩展
var="Hello"
echo "${var}"
echo "${var}_world"
echo "${var:-default}"
echo "${var:+value}"
echo "${var:?error}"
echo "${var:=default}"
# 字符串扩展
echo "${var:0:2}" # 子字符串
echo "${var#He}" # 移除前缀
echo "${var%lo}" # 移除后缀
echo "${var/He/Hi}" # 替换
echo "${var^^}" # 转为大写(bash 4+)
echo "${var,,}" # 转为小写(bash 4+)
# 路径扩展
echo "~" # 家目录
echo "~user" # 指定用户的家目录
echo "$HOME" # 家目录
echo "$(dirname "$0")" # 脚本所在目录
echo "$(basename "$0")" # 脚本文件名
# 引号扩展
single_quoted='This is $var' # 单引号不扩展变量
double_quoted="This is $var" # 双引号扩展变量
echo $single_quoted
echo $double_quoted5. Shell脚本实战案例
5.1 系统监控脚本
bash
#!/bin/bash
# 系统监控脚本
set -euo pipefail
# 日志函数
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
}
# 监控配置
CPU_THRESHOLD=80
MEMORY_THRESHOLD=80
DISK_THRESHOLD=80
CHECK_INTERVAL=60
LOG_FILE="system_monitor.log"
# 执行日志重定向
exec > >(tee -a "$LOG_FILE") 2>&1
# 检查CPU使用率
check_cpu() {
local cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
local cpu_usage_int=$(printf "%.0f" "$cpu_usage")
log_info "CPU usage: $cpu_usage%"
if [ "$cpu_usage_int" -gt "$CPU_THRESHOLD" ]; then
log_error "CPU usage exceeds threshold ($CPU_THRESHOLD%)"
return 1
fi
return 0
}
# 检查内存使用率
check_memory() {
local memory_usage=$(free | grep Mem | awk '{print $3/$2 * 100.0}')
local memory_usage_int=$(printf "%.0f" "$memory_usage")
log_info "Memory usage: $memory_usage%"
if [ "$memory_usage_int" -gt "$MEMORY_THRESHOLD" ]; then
log_error "Memory usage exceeds threshold ($MEMORY_THRESHOLD%)"
return 1
fi
return 0
}
# 检查磁盘使用率
check_disk() {
local disk_usage=$(df -h / | grep '/' | awk '{print $5}' | sed 's/%//')
log_info "Disk usage: $disk_usage%"
if [ "$disk_usage" -gt "$DISK_THRESHOLD" ]; then
log_error "Disk usage exceeds threshold ($DISK_THRESHOLD%)"
return 1
fi
return 0
}
# 检查进程状态
check_process() {
local process_name=$1
if pgrep "$process_name" > /dev/null; then
log_info "Process $process_name is running"
return 0
else
log_error "Process $process_name is not running"
return 1
fi
}
# 主函数
main() {
log_info "Starting system monitor"
while true; do
log_info "-----------------------------------"
log_info "Checking system status"
check_cpu
check_memory
check_disk
check_process "sshd"
check_process "nginx"
log_info "System check completed"
log_info "Sleeping for $CHECK_INTERVAL seconds"
sleep "$CHECK_INTERVAL"
done
}
# 执行主函数
main5.2 备份脚本
bash
#!/bin/bash
# 备份脚本
set -euo pipefail
# 配置
BACKUP_DIR="/backup"
SOURCE_DIRS=("/etc" "/var/www" "/home")
EXCLUDE_PATTERNS=("*.tmp" "*.log" "/home/*/.cache" "/home/*/.local/share")
RETENTION_DAYS=7
LOG_FILE="backup.log"
# 日志函数
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
}
# 执行日志重定向
exec > >(tee -a "$LOG_FILE") 2>&1
# 创建备份目录
create_backup_dir() {
local backup_date=$(date '+%Y-%m-%d_%H-%M-%S')
local backup_path="$BACKUP_DIR/$backup_date"
mkdir -p "$backup_path"
echo "$backup_path"
}
# 执行备份
perform_backup() {
local backup_path=$1
log_info "Starting backup to $backup_path"
# 构建 rsync 命令
local rsync_cmd="rsync -av --delete"
# 添加排除模式
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
rsync_cmd+=" --exclude='$pattern'"
done
# 执行备份
for dir in "${SOURCE_DIRS[@]}"; do
log_info "Backing up $dir"
eval "$rsync_cmd '$dir' '$backup_path/'"
done
log_info "Backup completed successfully"
}
# 创建压缩包
create_archive() {
local backup_path=$1
local archive_name="${backup_path}.tar.gz"
log_info "Creating archive $archive_name"
tar -czf "$archive_name" -C "$(dirname "$backup_path")" "$(basename "$backup_path")"
# 删除原始备份目录
rm -rf "$backup_path"
log_info "Archive created successfully"
echo "$archive_name"
}
# 清理旧备份
cleanup_old_backups() {
log_info "Cleaning up backups older than $RETENTION_DAYS days"
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +"$RETENTION_DAYS" -delete
log_info "Cleanup completed"
}
# 主函数
main() {
log_info "Starting backup script"
# 创建备份目录
local backup_path=$(create_backup_dir)
# 执行备份
perform_backup "$backup_path"
# 创建压缩包
local archive_name=$(create_archive "$backup_path")
# 清理旧备份
cleanup_old_backups
log_info "Backup script completed successfully"
log_info "Backup archive: $archive_name"
}
# 执行主函数
main5.3 自动化部署脚本
bash
#!/bin/bash
# 自动化部署脚本
set -euo pipefail
# 配置
REPO_URL="https://github.com/user/repo.git"
APP_DIR="/var/www/app"
TMP_DIR="/tmp/deploy"
BRANCH="main"
ENVIRONMENT="production"
LOG_FILE="deploy.log"
# 日志函数
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
}
# 执行日志重定向
exec > >(tee -a "$LOG_FILE") 2>&1
# 检查依赖
check_dependencies() {
log_info "Checking dependencies"
for cmd in git curl tar; do
if ! command -v "$cmd" > /dev/null; then
log_error "Command $cmd not found"
exit 1
fi
done
log_info "Dependencies check completed"
}
# 准备部署目录
prepare_deploy_dir() {
log_info "Preparing deploy directory"
rm -rf "$TMP_DIR"
mkdir -p "$TMP_DIR"
log_info "Deploy directory prepared"
}
# 克隆代码
clone_code() {
log_info "Cloning code from $REPO_URL"
git clone --branch "$BRANCH" "$REPO_URL" "$TMP_DIR"
log_info "Code cloned successfully"
}
# 安装依赖
install_dependencies() {
log_info "Installing dependencies"
cd "$TMP_DIR"
if [ -f "package.json" ]; then
npm install
elif [ -f "requirements.txt" ]; then
pip install -r requirements.txt
elif [ -f "pom.xml" ]; then
mvn install
else
log_info "No dependency file found"
fi
log_info "Dependencies installed successfully"
}
# 构建项目
build_project() {
log_info "Building project"
cd "$TMP_DIR"
if [ -f "package.json" ]; then
npm run build
elif [ -f "pom.xml" ]; then
mvn package
else
log_info "No build script found"
fi
log_info "Project built successfully"
}
# 部署应用
deploy_application() {
log_info "Deploying application to $APP_DIR"
# 停止服务
if systemctl is-active --quiet nginx; then
systemctl stop nginx
fi
# 备份旧应用
if [ -d "$APP_DIR" ]; then
backup_dir="$APP_DIR.$(date '+%Y-%m-%d_%H-%M-%S')"
mv "$APP_DIR" "$backup_dir"
log_info "Old application backed up to $backup_dir"
fi
# 复制新应用
mkdir -p "$APP_DIR"
cp -r "$TMP_DIR"/* "$APP_DIR/"
# 启动服务
if systemctl is-enabled --quiet nginx; then
systemctl start nginx
fi
log_info "Application deployed successfully"
}
# 验证部署
verify_deployment() {
log_info "Verifying deployment"
# 检查服务状态
if systemctl is-active --quiet nginx; then
log_info "Nginx service is running"
else
log_error "Nginx service is not running"
return 1
fi
# 检查应用响应
if curl -s -o /dev/null -w "%{http_code}" "http://localhost" | grep -q "200"; then
log_info "Application is responding"
else
log_error "Application is not responding"
return 1
fi
log_info "Deployment verified successfully"
return 0
}
# 清理临时文件
cleanup() {
log_info "Cleaning up temporary files"
rm -rf "$TMP_DIR"
log_info "Cleanup completed"
}
# 回滚部署
rollback() {
log_error "Deployment failed, rolling back"
# 查找最新备份
local backup=$(ls -1 "$APP_DIR.*" 2>/dev/null | sort -r | head -n 1)
if [ -n "$backup" ]; then
log_info "Rolling back to $backup"
# 停止服务
if systemctl is-active --quiet nginx; then
systemctl stop nginx
fi
# 恢复备份
rm -rf "$APP_DIR"
mv "$backup" "$APP_DIR"
# 启动服务
if systemctl is-enabled --quiet nginx; then
systemctl start nginx
fi
log_info "Rollback completed"
else
log_error "No backup found for rollback"
fi
}
# 主函数
main() {
log_info "Starting deployment script"
log_info "Environment: $ENVIRONMENT"
log_info "Branch: $BRANCH"
# 检查依赖
check_dependencies
# 准备部署目录
prepare_deploy_dir
# 克隆代码
clone_code
# 安装依赖
install_dependencies
# 构建项目
build_project
# 部署应用
deploy_application
# 验证部署
if ! verify_deployment; then
rollback
cleanup
exit 1
fi
# 清理临时文件
cleanup
log_info "Deployment script completed successfully"
}
# 执行主函数
main5.4 网络检测脚本
bash
#!/bin/bash
# 网络检测脚本
set -euo pipefail
# 配置
HOSTS=("google.com" "github.com" "1.1.1.1" "8.8.8.8")
PORT=80
TIMEOUT=5
LOG_FILE="network_check.log"
# 日志函数
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
}
# 执行日志重定向
exec > >(tee -a "$LOG_FILE") 2>&1
# 检查主机可达性
check_host() {
local host=$1
log_info "Checking host $host"
if ping -c 1 -W "$TIMEOUT" "$host" > /dev/null; then
log_info "$host is reachable"
return 0
else
log_error "$host is not reachable"
return 1
fi
}
# 检查端口可达性
check_port() {
local host=$1
local port=$2
log_info "Checking port $port on $host"
if nc -z -w "$TIMEOUT" "$host" "$port"; then
log_info "Port $port on $host is open"
return 0
else
log_error "Port $port on $host is closed or filtered"
return 1
fi
}
# 检查HTTP响应
check_http() {
local host=$1
local port=$2
log_info "Checking HTTP response from $host:$port"
local url="http://$host:$port"
local response=$(curl -s -o /dev/null -w "%{http_code}" -m "$TIMEOUT" "$url")
if [ "$response" -eq 200 ]; then
log_info "HTTP response from $host:$port is $response (OK)"
return 0
else
log_error "HTTP response from $host:$port is $response"
return 1
fi
}
# 检查网络接口
check_interfaces() {
log_info "Checking network interfaces"
ip addr | grep -E 'inet ' | grep -v '127.0.0.1'
log_info "Network interfaces checked"
}
# 检查路由表
check_routes() {
log_info "Checking routing table"
ip route
log_info "Routing table checked"
}
# 主函数
main() {
log_info "Starting network check"
# 检查网络接口
check_interfaces
# 检查路由表
check_routes
# 检查每个主机
for host in "${HOSTS[@]}"; do
log_info "-----------------------------------"
check_host "$host"
check_port "$host" "$PORT"
check_http "$host" "$PORT"
done
log_info "Network check completed"
}
# 执行主函数
main5.5 日志分析脚本
bash
#!/bin/bash
# 日志分析脚本
set -euo pipefail
# 配置
LOG_FILES=("/var/log/syslog" "/var/log/nginx/access.log" "/var/log/nginx/error.log")
OUTPUT_DIR="/tmp/log_analysis"
TOP_N=10
LOG_FILE="log_analysis.log"
# 日志函数
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
}
# 执行日志重定向
exec > >(tee -a "$LOG_FILE") 2>&1
# 创建输出目录
create_output_dir() {
mkdir -p "$OUTPUT_DIR"
log_info "Created output directory $OUTPUT_DIR"
}
# 分析系统日志
analyze_syslog() {
local log_file="/var/log/syslog"
if [ -f "$log_file" ]; then
log_info "Analyzing system log $log_file"
# 提取错误信息
grep -i "error" "$log_file" > "$OUTPUT_DIR/syslog_errors.txt"
# 统计错误类型
cut -d' ' -f5- "$OUTPUT_DIR/syslog_errors.txt" | sort | uniq -c | sort -nr | head -n "$TOP_N" > "$OUTPUT_DIR/syslog_error_stats.txt"
log_info "System log analysis completed"
else
log_error "System log file $log_file not found"
fi
}
# 分析Nginx访问日志
analyze_nginx_access() {
local log_file="/var/log/nginx/access.log"
if [ -f "$log_file" ]; then
log_info "Analyzing Nginx access log $log_file"
# 提取IP地址
awk '{print $1}' "$log_file" | sort | uniq -c | sort -nr | head -n "$TOP_N" > "$OUTPUT_DIR/nginx_top_ips.txt"
# 提取请求路径
awk '{print $7}' "$log_file" | sort | uniq -c | sort -nr | head -n "$TOP_N" > "$OUTPUT_DIR/nginx_top_paths.txt"
# 提取状态码
awk '{print $9}' "$log_file" | sort | uniq -c | sort -nr > "$OUTPUT_DIR/nginx_status_codes.txt"
# 提取用户代理
awk -F\" '{print $6}' "$log_file" | sort | uniq -c | sort -nr | head -n "$TOP_N" > "$OUTPUT_DIR/nginx_top_user_agents.txt"
log_info "Nginx access log analysis completed"
else
log_error "Nginx access log file $log_file not found"
fi
}
# 分析Nginx错误日志
analyze_nginx_error() {
local log_file="/var/log/nginx/error.log"
if [ -f "$log_file" ]; then
log_info "Analyzing Nginx error log $log_file"
# 提取错误信息
sort "$log_file" | uniq -c | sort -nr | head -n "$TOP_N" > "$OUTPUT_DIR/nginx_error_stats.txt"
log_info "Nginx error log analysis completed"
else
log_error "Nginx error log file $log_file not found"
fi
}
# 生成报告
generate_report() {
local report_file="$OUTPUT_DIR/report.txt"
log_info "Generating report $report_file"
cat > "$report_file" << EOF
Log Analysis Report
==================
Date: $(date '+%Y-%m-%d %H:%M:%S')
System Log Analysis
------------------
Top $TOP_N Error Types:
$(cat "$OUTPUT_DIR/syslog_error_stats.txt" 2>/dev/null || echo "No data")
Nginx Access Log Analysis
------------------------
Top $TOP_N IP Addresses:
$(cat "$OUTPUT_DIR/nginx_top_ips.txt" 2>/dev/null || echo "No data")
Top $TOP_N Request Paths:
$(cat "$OUTPUT_DIR/nginx_top_paths.txt" 2>/dev/null || echo "No data")
Status Codes:
$(cat "$OUTPUT_DIR/nginx_status_codes.txt" 2>/dev/null || echo "No data")
Top $TOP_N User Agents:
$(cat "$OUTPUT_DIR/nginx_top_user_agents.txt" 2>/dev/null || echo "No data")
Nginx Error Log Analysis
-----------------------
Top $TOP_N Error Types:
$(cat "$OUTPUT_DIR/nginx_error_stats.txt" 2>/dev/null || echo "No data")
EOF
log_info "Report generated successfully"
cat "$report_file"
}
# 主函数
main() {
log_info "Starting log analysis"
# 创建输出目录
create_output_dir
# 分析系统日志
analyze_syslog
# 分析Nginx访问日志
analyze_nginx_access
# 分析Nginx错误日志
analyze_nginx_error
# 生成报告
generate_report
log_info "Log analysis completed"
log_info "Results saved to $OUTPUT_DIR"
}
# 执行主函数
main4. Shell脚本性能优化
4.1 性能优化技巧
4.1.1 减少命令执行
bash
# 不好的写法
for file in *.txt; do
wc -l $file
grep "error" $file
sort $file > sorted_$file
done
# 好的写法
for file in *.txt; do
lines=$(wc -l < $file)
errors=$(grep -c "error" $file)
echo "$file: $lines lines, $errors errors"
sort $file > sorted_$file
done4.1.2 使用内置命令
bash
# 不好的写法
for i in $(seq 1 1000); do
echo $i
done
# 好的写法
for ((i=1; i<=1000; i++)); do
echo $i
done
# 不好的写法
echo "Current directory: $(pwd)"
# 好的写法
echo "Current directory: $PWD"4.1.3 减少管道和重定向
bash
# 不好的写法
cat file.txt | grep "pattern" | sort | uniq > output.txt
# 好的写法
grep "pattern" file.txt | sort -u > output.txt
# 不好的写法
for file in *.txt; do
cat $file >> all.txt
done
# 好的写法
cat *.txt > all.txt4.1.4 使用更高效的命令
bash
# 不好的写法
for i in $(ls *.txt); do
echo $i
done
# 好的写法
for file in *.txt; do
echo $file
done
# 不好的写法
find . -name "*.txt" | xargs grep "pattern"
# 好的写法
find . -name "*.txt" -exec grep "pattern" {} \;4.1.5 内存管理
bash
# 不好的写法
large_file=$(cat large.txt)
echo "$large_file"
# 好的写法
cat large.txt
# 不好的写法
for line in $(cat file.txt); do
echo $line
done
# 好的写法
while IFS= read -r line; do
echo $line
done < file.txt4.2 性能测试
bash
# 测试脚本执行时间
time script.sh
# 详细时间统计
time -p script.sh
# 内存使用
time -v script.sh
# 比较不同方法的性能
method1() {
for ((i=1; i<=10000; i++)); do
echo $i > /dev/null
done
}
method2() {
seq 1 10000 > /dev/null
}
echo "Method 1:"
time method1
echo "Method 2:"
time method2
# 分析脚本执行
trace() {
local script=$1
bash -x "$script" 2>&1 | grep -E '^\+ ' | head -n 20
}
trace script.sh5. Shell脚本安全最佳实践
5.1 安全隐患
5.1.1 常见安全问题
- 命令注入:使用未过滤的用户输入
- 路径遍历:允许访问不应访问的文件
- 权限提升:以 root 权限执行不必要的操作
- 敏感信息泄露:在日志中记录密码等敏感信息
- 不安全的临时文件:使用可预测的临时文件名
- 脚本注入:通过环境变量或配置文件注入恶意代码
5.1.2 防范措施
- 输入验证:验证所有用户输入
- 使用引用:正确引用变量,防止命令注入
- 最小权限:以最小必要权限执行脚本
- 安全的临时文件:使用 mktemp 创建临时文件
- 敏感信息保护:避免在日志中记录敏感信息
- 文件权限:设置正确的文件权限
- 环境变量清理:清理不必要的环境变量
5.2 安全编码实践
bash
# 输入验证
validate_input() {
local input=$1
if [[ ! $input =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo "Invalid input"
exit 1
fi
}
# 使用引用
user_input="$(echo "$1" | sed 's/[^a-zA-Z0-9_-]//g')"
echo "Processing: "$user_input""
# 最小权限
if [ "$(id -u)" -eq 0 ]; then
echo "Do not run as root"
exit 1
fi
# 安全的临时文件
temp_file=$(mktemp /tmp/script.XXXXXX)
trap "rm -f '$temp_file'" EXIT
# 敏感信息保护
password="$(echo "$2" | sed 's/./*/g')"
echo "Password: $password"
# 文件权限
chmod 700 script.sh
# 环境变量清理
unset HISTFILE
unset SSH_AUTH_SOCK
# 安全的命令执行
command=$(printf "%q" "$user_command")
eval "$command"
# 检查文件存在性
if [ -f "$file" ]; then
# 安全地读取文件
while IFS= read -r line; do
echo "$line"
done < "$file"
fi
# 防止路径遍历
file=$(realpath "$file")
base_dir=$(realpath "/safe/dir")
if [[ "$file" == "$base_dir"* ]]; then
# 安全地访问文件
cat "$file"
else
echo "Access denied"
exit 1
fi6. 总结与展望
6.1 Shell脚本的优势
- 简单易用:语法简洁,易于学习和使用
- 强大的文本处理能力:擅长处理文本数据
- 系统集成:与操作系统紧密集成
- 跨平台:在大多数UNIX/Linux系统上可用
- 无需编译:直接执行,快速开发
- 丰富的命令:可以调用系统的各种命令
6.2 Shell脚本的局限性
- 性能:相比编译型语言,执行速度较慢
- 复杂逻辑:处理复杂逻辑时代码可能变得难以维护
- 可移植性:不同Shell之间存在差异
- 错误处理:错误处理机制相对简单
- 安全性:存在安全隐患,需要特别注意
- 调试:调试工具相对有限
6.3 Shell脚本的未来发展
- bash 5+:提供更多高级特性,如关联数组、模式匹配等
- zsh:提供更丰富的功能和更好的用户体验
- fish:提供更现代的Shell体验,注重用户友好性
- ShellCheck:静态分析工具,帮助发现脚本中的问题
- shfmt:Shell脚本格式化工具,提高代码质量
- Docker:结合容器技术,实现更一致的执行环境
- 云原生:在云环境中用于自动化部署和管理
6.4 学习建议
- 掌握基础:熟悉Shell脚本的基本语法和命令
- 实践项目:通过实际项目练习,巩固所学知识
- 学习高级特性:掌握函数、数组、正则表达式等高级特性
- 阅读优秀脚本:学习他人编写的优秀脚本
- 使用工具:使用ShellCheck等工具检查脚本质量
- 关注安全:了解Shell脚本的安全隐患和防范措施
- 持续学习:关注Shell的最新发展和最佳实践
- 结合其他工具:与Python、Perl等语言结合使用
6.5 资源推荐
官方文档:
书籍:
- 《Shell Scripting: Expert Recipes for Linux, Bash, and More》
- 《Learning the bash Shell》
- 《Advanced Bash-Scripting Guide》
- 《Linux Command Line and Shell Scripting Bible》
在线资源:
工具:
- ShellCheck:静态分析工具
- shfmt:Shell脚本格式化工具
- bashdb:Bash调试器
- htop:进程监控工具
- ltrace/strace:系统调用跟踪工具
通过本文的学习,您应该已经掌握了Shell脚本的高级特性和应用场景。Shell脚本是一种强大的工具,可以帮助您自动化各种系统管理任务,提高工作效率。随着您的不断实践和学习,您将能够编写更加复杂、高效、安全的Shell脚本,解决实际工作中的各种问题。