
本文记录一次 Linux Shell 脚本编程实验包含三个 Bash 脚本图片批处理脚本支持质量压缩、分辨率压缩、文本水印、批量重命名、PNG/SVG 转 JPG。世界杯运动员数据统计脚本统计年龄分布、位置分布、姓名长短、年龄极值。Web 访问日志分析脚本统计来源主机、来源 IP、热门 URL、状态码、4XX 错误等。文中代码已在原始实验基础上做了修正和整理适合作为 Bash、awk、ImageMagick 的练习参考。数据来源世界杯数据https://c4pr1c3.github.io/LinuxSysAdmin/exp/chap0x04/worldcupplayerinfo.tsvWeb 日志https://c4pr1c3.github.io/LinuxSysAdmin/exp/chap0x04/web_log.tsv.7z本文仅用于学习交流。实验环境与依赖LinuxUbuntu / Debian 系均可ShellBash 5.x图片处理ImageMagick文本处理awk / gawk下载与解压wget、p7zip-full安装依赖sudoaptupdatesudoaptinstall-yimagemagickgawkwgetp7zip-full任务一图片批处理脚本功能说明脚本支持以下功能可单独使用也可组合使用对 JPEG/JPG 图片进行质量压缩对 JPG/JPEG/PNG/SVG 图片在保持宽高比前提下压缩分辨率批量添加自定义文本水印批量重命名统一添加前缀或后缀不影响扩展名将 PNG/SVG 图片转换为 JPG使用方式./image_batch.sh-q85-d./imgs ./image_batch.sh-r1024x768-d./imgs ./image_batch.sh-wju-d./imgs ./image_batch.sh-p2025_-d./imgs ./image_batch.sh-s_backup-d./imgs ./image_batch.sh-c-d./imgs组合使用示例./image_batch.sh-q85-r1024x768-w机密-p2025_-c-d./imgs完整脚本保存为image_batch.sh#!/bin/bash# 图片批处理脚本# 支持质量压缩、分辨率压缩、文本水印、批量重命名、PNG/SVG 转 JPGusage(){catEOF 图片处理脚本 用法: ./image_batch.sh [选项] -d 图片目录 选项: -q 质量 指定输出图片质量1-100仅对 JPEG/JPG 有效 -r 分辨率 指定输出图片分辨率如 800x600 -w 水印文本 为图片添加文本水印 -p 前缀 为文件名添加前缀 -s 后缀 为文件名添加后缀 -c 将 PNG/SVG 图片转换为 JPG 格式 -d 图片目录 指定包含图片的目录 -h 显示帮助 示例: ./image_batch.sh -q 85 -r 1024x768 -w 机密 -p 2025_ -c -d ./imgs 依赖: ImageMagick EOFexit1}check_dependencies(){if!command-vconvert/dev/null21;thenecho错误: 未找到 ImageMagick 的 convert 命令请先安装sudo apt install imagemagickexit1fi}QUALITY0RESIZEWATERMARKPREFIXSUFFIXCONVERT_JPG0IMG_DIRwhilegetoptsq:r:w:p:s:cd:hopt;docase$optinq)QUALITY$OPTARG;;r)RESIZE$OPTARG;;w)WATERMARK$OPTARG;;p)PREFIX$OPTARG;;s)SUFFIX$OPTARG;;c)CONVERT_JPG1;;d)IMG_DIR$OPTARG;;h|*)usage;;esacdone[-z$IMG_DIR]usage[!-d$IMG_DIR]{echo错误: 图片目录不存在:$IMG_DIR;exit1;}check_dependenciesprocess_image(){localfile$1localfilename extensiondirnew_namefilename$(basename--$file)extension${filename##*.}extension$(echo$extension|tr[:upper:][:lower:])dir$(dirname--$file)# 批量重命名if[-n$PREFIX]||[-n$SUFFIX];thennew_name${dir}/${PREFIX}${filename%.*}${SUFFIX}.${extension}mv--$file$new_namefile$new_namefilename$(basename--$file)extension${filename##*.}extension$(echo$extension|tr[:upper:][:lower:])fi# 压缩分辨率if[-n$RESIZE];thencase$extensioninjpg|jpeg|png|svg)localresized_file${dir}/${filename%.*}_resized.${extension}convert$file-resize$RESIZE$resized_filefile$resized_filefilename$(basename--$file)extension${filename##*.}extension$(echo$extension|tr[:upper:][:lower:]);;esacfi# 添加水印if[-n$WATERMARK];thenlocalwatermarked_file${dir}/${filename%.*}_watermarked.${extension}convert$file-gravitysoutheast-pointsize36\-fillred-annotate1010$WATERMARK$watermarked_filefile$watermarked_filefilename$(basename--$file)extension${filename##*.}extension$(echo$extension|tr[:upper:][:lower:])fi# 格式转换if[$CONVERT_JPG-eq1];thencase$extensioninpng|svg)localjpg_file${dir}/${filename%.*}_converted.jpgconvert$file-backgroundwhite-flatten$jpg_filefile$jpg_filefilename$(basename--$file)extensionjpg;;esacfi# 质量压缩if[$QUALITY-gt0][$QUALITY-le100];thencase$extensioninjpg|jpeg)localcompressed_file${dir}/${filename%.*}_compressed.${extension}convert$file-quality$QUALITY$compressed_filefile$compressed_file;;esacfi}find$IMG_DIR-typef\(-iname*.jpg-o-iname*.jpeg-o-iname*.png-o-iname*.svg\)-print0|whileIFSread-r-dfile;doprocess_image$filedoneecho图片处理完成结果保存在:$IMG_DIR结果说明运行后会在原目录生成带后缀的新图片例如xxx_compressed.jpg质量压缩后的图片xxx_resized.jpg分辨率压缩后的图片xxx_watermarked.jpg添加水印后的图片xxx_converted.jpgPNG/SVG 转换后的 JPG 图片任务二2014 世界杯运动员数据统计获取数据wgethttps://c4pr1c3.github.io/LinuxSysAdmin/exp/chap0x04/worldcupplayerinfo.tsv数据列说明Group Country Rank Jersey Position Age Selections Club Player Captain其中第 5 列Position场上位置第 6 列Age年龄第 9 列Player球员姓名完整脚本保存为worldcup_stats.sh#!/bin/bash# 世界杯数据统计脚本usage(){catEOF 世界杯数据统计脚本 用法: ./worldcup_stats.sh [选项] 数据文件.tsv 选项: -o 目录 指定输出目录默认 ./stats -h 显示帮助 统计内容: 1. 年龄区间分布 2. 场上位置分布 3. 最长/最短姓名 4. 最大/最小年龄 EOFexit0}output_dir./statswhilegetopts:o:hopt;docase$optino)output_dir$OPTARG;;h)usage;;*)echo无效选项: -$OPTARG2;exit1;;esacdoneshift$((OPTIND-1))[$#-lt1]{echo必须指定数据文件2;exit1;}data_file$1[!-f$data_file]{echo文件不存在:$data_file2;exit1;}mkdir-p$output_dirtimestamp$(date%Y%m%d_%H%M%S)report_file${output_dir}/worldcup_stats_${timestamp}.txtexec(tee$report_file)21echo 开始分析 [$(date)] echoecho 年龄分布 awk-F\t NR 1 $6 ! { total age $6 0 if (age 20) under20 else if (age 30) between else over30 } END { printf 20岁以下: %d (%.2f%%)\n, under20, (under20/total)*100 printf 20-30岁: %d (%.2f%%)\n, between, (between/total)*100 printf 30岁以上: %d (%.2f%%)\n, over30, (over30/total)*100 }$data_fileechoecho 位置分布 awk-F\t NR 1 $5 ! { pos $5 gsub(/ /, , pos) positions[pos] total } END { for (p in positions) printf %s: %d (%.2f%%)\n, p, positions[p], (positions[p]/total)*100 }$data_file|sort-k2-nrechoecho 姓名统计 awk-F\t NR 1 $9 ! { name $9 len length(name) if (len max_len) { max_len len; max_name name } else if (len max_len) { max_name max_name | name } if (min_len 0 || len min_len) { min_len len; min_name name } else if (len min_len) { min_name min_name | name } } END { printf 最长姓名(%d字符): %s\n, max_len, max_name printf 最短姓名(%d字符): %s\n, min_len, min_name }$data_fileechoecho 年龄极值 awk-F\t NR 1 $6 ! { age $6 0 name $9 if (max_age 0 || age max_age) { max_age age; max_name name } else if (age max_age) { max_name max_name | name } if (min_age 0 || age min_age) { min_age age; min_name name } else if (age min_age) { min_name min_name | name } } END { printf 最年长(%d岁): %s\n, max_age, max_name printf 最年轻(%d岁): %s\n, min_age, min_name }$data_fileechoecho 分析完成 [$(date)] echo结果已保存至:$report_file运行方式chmodx worldcup_stats.sh ./worldcup_stats.sh worldcupplayerinfo.tsv-o./stats输出报告会保存到./stats目录包含年龄分布、位置分布、姓名统计和年龄极值。任务三Web 访问日志分析获取数据wgethttps://c4pr1c3.github.io/LinuxSysAdmin/exp/chap0x04/web_log.tsv.7z 7z x web_log.tsv.7z完整脚本保存为web_log_stats.sh#!/bin/bash# Web 访问日志分析脚本usage(){catEOF Web 日志分析脚本 用法: ./web_log_stats.sh [选项] 日志文件 选项: -o 目录 指定输出目录默认 ./log_stats -h 显示帮助 统计内容: 1. 访问来源主机 TOP 100 2. 访问来源 IP TOP 100 3. 最频繁访问 URL TOP 100 4. 响应状态码统计 5. 4XX 状态码对应 TOP 10 URL 6. 指定 URL 的 TOP 100 访问来源 EOFexit0}output_dir./log_statswhilegetopts:o:hopt;docase$optino)output_dir$OPTARG;;h)usage;;*)echo无效选项: -$OPTARG2;exit1;;esacdoneshift$((OPTIND-1))[$#-lt1]{echo必须指定日志文件2;exit1;}log_file$1[!-f$log_file]{echo文件不存在:$log_file2;exit1;}mkdir-p$output_dirtimestamp$(date%Y%m%d_%H%M%S)report_file${output_dir}/full_report_${timestamp}.txtsave_output(){localtitle$1localcontent$2localsuffix$3{echoecho$titleecho$content}|tee-a$report_file{echo$titleecho$content}${output_dir}/${suffix}_${timestamp}.txt}# 访问来源主机 TOP 100host_stats$(awk -F \t NR 1 { hosts[$1] } END { for (h in hosts) print hosts[h], h } $log_file | sort -nr | head -n 100)save_output 访问来源主机 TOP 100 $host_statstop_hosts# 访问来源 IP TOP 100ip_stats$(awk -F \t NR 1 $1~ /^[0-9]\.[0-9]\.[0-9]\.[0-9]$/ { ips[$1] } END { for (ip in ips) print ips[ip], ip } $log_file | sort -nr | head -n 100)save_output 访问来源 IP TOP 100 $ip_statstop_ips# 最频繁访问 URL TOP 100url_stats$(awk -F \t NR 1 { urls[$5] } END { for (u in urls) print urls[u], u } $log_file | sort -nr | head -n 100)save_output 最频繁访问 URL TOP 100 $url_statstop_urls# 响应状态码统计status_stats$(awk -F \t NR 1 { codes[$6]; total } END { for (c in codes) printf 状态码 %s: %d 次(%.2f%%)\n, c, codes[c], (codes[c]/total)*100 } $log_file | sort -n)save_output 响应状态码统计 $status_statsstatus_codes# 4XX 错误 TOP 10 URLfourxx_stats$(awk -F \t NR 1 $6~ /^4/ { key $6SUBSEP$5count[key] } END { for (key in count) { split(key, arr, SUBSEP) printf %s %s %d\n, arr[1], arr[2], count[key] } } $log_file | sort -k1,1 -k3,3nr | awk { code $1if (code ! prev) { if (prev ! )print----------print状态码 code:n0}if(n10){print $3 次访问: $2}prevcode}) save_output 4XX 错误 TOP 10 URL $fourxx_stats 4xx_urls # 指定 URL 的 TOP 100 访问来源 echo read -r -p 请输入要查询的 URL: target_url specified_url_stats$(awk -F \t -v url$target_url NR1$5url{hosts[$1]}END{for(hinhosts)print hosts[h], h}$log_file|sort-nr|head-n100) save_output [${target_url}]TOP100访问来源 $specified_url_stats custom_url_sources echo echo 分析完成报告已保存至:$report_file运行方式chmodx web_log_stats.sh ./web_log_stats.sh web_log.tsv-o./log_stats运行过程中会提示输入一个 URL用于统计该 URL 的 TOP 100 访问来源。结果节选访问来源主机 TOP 100 节选6530 edams.ksc.nasa.gov 4846 piweba4y.prodigy.com 4791 163.206.89.4 4607 piweba5y.prodigy.com 4416 piweba3y.prodigy.com访问来源 IP TOP 100 节选4791 163.206.89.4 1435 128.217.62.1 1360 163.205.1.19 1292 163.205.3.104 1256 163.205.156.16最频繁访问 URL TOP 100 节选97410 /images/NASA-logosmall.gif 75337 /images/KSC-logosmall.gif 67448 /images/MOSAIC-logosmall.gif 67068 /images/USA-logosmall.gif 66444 /images/WORLD-logosmall.gif响应状态码统计状态码 200: 1398987 次 (89.11%) 状态码 302: 26497 次 (1.69%) 状态码 304: 134146 次 (8.54%) 状态码 403: 171 次 (0.01%) 状态码 404: 10055 次 (0.64%) 状态码 500: 3 次 (0.00%) 状态码 501: 27 次 (0.00%)4XX 错误 TOP 10 URL 节选状态码 404: 1337 次访问: /pub/winvn/readme.txt 1185 次访问: /pub/winvn/release.txt 683 次访问: /shuttle/missions/STS-69/mission-STS-69.html 319 次访问: /images/nasa-logo.gif 253 次访问: /shuttle/missions/sts-68/ksc-upclose.gif指定 URL 查询示例请输入要查询的 URL: /history/mercury/mr-3/mr-3.html/mr-3-3patch.gif [/history/mercury/mr-3/mr-3.html/mr-3-3patch.gif] TOP 100 访问来源 1 128.158.42.143踩坑与改进遍历文件名要防空格原脚本使用find ... | while read file文件名含空格时会出错。改为find ... -print0配合while IFS read -r -d 更稳妥。扩展名统一小写.JPG、.JPEG和.jpg、.jpeg应统一处理否则质量压缩和格式转换可能漏掉文件。awk 多维数组兼容性urls[$5][$6]是 gawk 扩展写法在 mawk 中可能报错。使用SUBSEP构造复合键更通用key $6 SUBSEP $5 count[key]输出目录要在参数解析后初始化如果脚本先创建默认目录、后解析-o报告可能仍写到默认目录。应先解析参数再创建目录和报告文件。大日志文件优先用 awk 单次遍历多次grep、sort、uniq会重复读取文件。日志较大时用 awk 一次遍历完成计数再排序输出效率更高。总结本文通过三个 Bash 脚本覆盖了 Linux Shell 编程中常见的几个方向命令行参数解析getopts文件遍历与安全处理find -print0、read -d 图片批处理ImageMagick 的convert文本统计awk 字段处理、数组计数、排序日志分析TOP N、状态码百分比、4XX 错误定位这些脚本可以直接运行也适合继续扩展例如为图片脚本增加递归目录、输出目录参数为世界杯统计增加按国家、俱乐部统计为日志分析增加时间范围过滤、User-Agent 统计将脚本封装成 systemd 定时任务或 CI 任务