
1. 从一条运维日志说起为什么要用脚本自动创建知识库页面手里维护着十几台服务器每天产生的日志文件加起来少说也有几百兆。以前遇到问题排查完顺手把关键日志往知识库Wiki里一贴写个标题、加个标签就算归档了。但时间一长重复劳动的问题就暴露出来了同一个服务每周都要贴一次日志每次都要手动打开浏览器、登录、新建页面、复制粘贴、调格式一套流程下来少说五分钟。更麻烦的是有时候半夜处理完故障人已经困得不行第二天再想补记录日志文件早被轮转覆盖了。后来我就琢磨能不能写个脚本把“创建Wiki页面”和“把日志内容贴进去”这两件事串起来一条命令搞定。这个需求听起来简单但真动手做的时候涉及的东西还不少Wiki系统的API怎么调、日志文件怎么读取、内容格式怎么处理、页面标题怎么命名才能不重复、失败了怎么重试。这些问题一个个解决下来也算攒了一套比较成熟的方案。这篇文章就是把这套方案完整拆开讲清楚。不管你是刚接触运维自动化的新手还是已经写过一些脚本的老手都能从中找到可以直接复用的思路和代码。核心关键词就三个Shell脚本、Wiki页面创建、日志内容写入。我会从整体设计思路讲到具体实现再到踩过的坑和排查技巧尽量做到看完就能上手。2. 整体设计思路与方案选型2.1 为什么选择Shell而不是Python或其他语言很多人第一反应可能是用Python毕竟requests库调API很方便。但我最终选了Shell原因有几个。第一运维场景下Shell的普适性最强几乎每台Linux服务器都自带bash不需要额外装运行时环境。第二这个任务本质上是“读文件、发请求、写结果”逻辑不复杂用Shell完全够用没必要引入Python的依赖管理。第三Shell和cron、systemd这些系统工具配合得天衣无缝定时执行或者事件触发都很方便。当然Shell处理JSON确实不如Python优雅。但现在的Wiki系统API大多支持简单的表单提交或者REST接口用curl配合几个参数就能搞定不一定非要解析复杂的JSON响应。实测下来只要把curl的用法吃透Shell方案的稳定性和可维护性完全不输Python。2.2 Wiki API的两种主流接入方式不同Wiki系统的API设计差异很大但归纳起来无非两种模式。一种是REST风格通过POST请求创建页面请求体里带标题和内容返回JSON格式的结果。另一种是表单提交风格模拟浏览器行为把参数以form-data的形式发出去。前者更现代后者兼容性更好。我用的这套Wiki系统支持REST接口创建页面的端点大概是这样的结构向/api/v1/pages发一个POST请求请求头里带认证Token请求体里包含title、content、space这几个字段。认证方式用的是Bearer Token比传统的用户名密码更安全也更容易在脚本里管理。注意不管用哪种方式认证信息绝对不能硬编码在脚本里。我一般把Token放在单独的环境变量文件里脚本启动时source一下权限设成600只有当前用户能读。2.3 日志内容的预处理策略日志文件直接贴进Wiki会有几个问题。第一日志里可能包含敏感信息比如IP地址、内部域名、用户ID直接公开不合适。第二日志文件动辄几十兆全贴进去页面加载会很慢。第三纯文本日志在Wiki里显示没有高亮可读性差。所以脚本里必须加预处理环节。我的做法是先用grep过滤出关键行比如ERROR、WARN级别的日志再用sed把敏感信息替换成占位符最后用tail截取最近N行确保内容量可控。如果Wiki支持Markdown格式还可以在内容外面包一层代码块标记让日志显示得更整齐。2.4 页面命名与去重逻辑页面标题如果每次都一样第二次创建就会失败或者覆盖。我的方案是用“服务名日期序号”的格式比如nginx-error-20250115-01。脚本里先查一下当天是否已有同名页面如果有就递增序号。这样既保证了唯一性又能通过标题快速定位到某天的日志。查询页面是否存在的接口通常是GET请求返回200表示存在404表示不存在。但有些Wiki系统对不存在的页面返回的是200加空内容这就需要根据具体系统的行为来调整判断逻辑。我踩过这个坑后面会详细说。3. 核心细节解析与实操要点3.1 认证Token的安全管理Token泄露的后果不用多说别人拿到你的Token就能以你的身份创建、修改、删除页面。所以第一步就是把Token管好。我的做法是创建一个~/.wiki_env文件内容类似export WIKI_API_URLhttps://wiki.example.com/api/v1 export WIKI_TOKENyour_token_here export WIKI_SPACEOPS然后chmod 600 ~/.wiki_env确保只有自己能读。脚本开头加一行source ~/.wiki_env后面直接用变量。这样即使脚本被其他人看到也拿不到真实的Token。提示如果团队多人共用脚本可以考虑用密钥管理服务动态获取Token或者给每个成员分配独立的Token。千万不要图省事把Token写在脚本注释里。3.2 日志文件的读取与过滤读取日志看起来简单但细节很多。首先要判断文件是否存在、是否可读其次要考虑文件正在被写入的情况。如果日志文件正在被写入直接cat可能会读到不完整的内容这时候用tail更安全。过滤环节我一般用这样的组合grep -E ERROR|WARN|FATAL $LOG_FILE | tail -n 500先筛出关键级别再取最近500行。500这个数字是拍脑袋定的吗不是。我观察过大部分故障的关键信息集中在前200行以内500行留了足够余量同时又不至于让页面太大。如果日志特别密集可以调小到200行如果排查的是复杂问题可以调到1000行。敏感信息替换用sedsed -E s/[0-9]\.[0-9]\.[0-9]\.[0-9]/[IP_REDACTED]/g | \ sed -E s/[a-zA-Z0-9._%-][a-zA-Z0-9.-]\.[a-zA-Z]{2,}/[EMAIL_REDACTED]/g这两条规则分别处理IP地址和邮箱地址。实际使用中还可以根据业务特点增加规则比如替换内部域名、用户ID等。3.3 内容格式的转换与包装Wiki页面内容支持多种格式常见的有Markdown、HTML、纯文本。如果API接受Markdown那最方便直接把日志包在代码块里CONTENT\\\\n$(cat /tmp/filtered_log)\n\\\但要注意Shell里的反引号和特殊字符需要转义否则会被解释成命令替换。我一般先把内容写到临时文件再用--data-binary file的方式传给curl避免转义地狱。如果API只接受HTML那就需要把日志内容做HTML转义把、、这些字符替换成实体。这一步用sed也能做但规则比较多建议写个小函数封装起来。3.4 请求发送与结果校验curl是核心工具参数配置直接影响成败。我常用的配置是这样的curl -s -X POST $WIKI_API_URL/pages \ -H Authorization: Bearer $WIKI_TOKEN \ -H Content-Type: application/json \ -d {\title\:\$TITLE\,\content\:\$CONTENT\,\space\:\$WIKI_SPACE\} \ -o /tmp/wiki_response.json \ -w %{http_code}这里用-w %{http_code}把HTTP状态码输出到标准输出方便脚本判断。-o把响应体写到文件便于出错时查看详情。-s关闭进度条让输出干净。拿到状态码后要判断200或201表示成功401表示Token无效403表示权限不足409表示页面已存在500表示服务端错误。不同状态码对应不同的处理策略后面排查章节会详细讲。4. 完整实操流程与核心环节实现4.1 环境准备与依赖检查动手之前先确认环境。需要的东西不多bash 4.0以上、curl 7.0以上、grep和sed是系统自带的。检查命令bash --version | head -1 curl --version | head -1如果curl版本太低可能不支持某些参数建议升级到最新稳定版。另外确认网络能通到Wiki服务器curl -s -o /dev/null -w %{http_code} $WIKI_API_URL/health返回200说明网络和认证都没问题。这一步看似多余但能提前排除很多低级错误。4.2 脚本主体结构拆解整个脚本我分成五个函数每个函数只做一件事load_config加载环境变量检查必要参数是否齐全prepare_content读取日志、过滤、脱敏、包装格式check_page_exists查询页面是否已存在create_page发送创建请求cleanup清理临时文件主流程就是依次调用这五个函数任何一步失败就输出错误信息并退出。这种结构的好处是逻辑清晰出问题容易定位。比如创建失败只需要看是prepare_content的问题还是create_page的问题。4.3 页面存在性检查的实现细节查询页面是否存在的接口调用check_page_exists() { local title$1 local http_code http_code$(curl -s -o /dev/null -w %{http_code} \ -H Authorization: Bearer $WIKI_TOKEN \ $WIKI_API_URL/pages?title$(urlencode $title)) if [ $http_code 200 ]; then return 0 else return 1 fi }这里有个坑标题里的特殊字符需要URL编码否则查询会失败。urlencode可以用jq或者python实现但为了不引入额外依赖我写了个纯Shell版本urlencode() { local string$1 local length${#string} for (( i 0; i length; i )); do local c${string:i:1} case $c in [a-zA-Z0-9.~_-]) printf $c ;; *) printf %%%02X $c ;; esac done }这个函数处理中文标题也没问题因为它是按字节编码的。4.4 创建请求的完整参数配置创建页面的请求体我用heredoc构造避免转义问题create_page() { local title$1 local content_file$2 local response_file/tmp/wiki_create_response.json local payload payload$(jq -n \ --arg title $title \ --arg space $WIKI_SPACE \ --rawfile content $content_file \ {title: $title, space: $space, content: $content}) local http_code http_code$(curl -s -X POST $WIKI_API_URL/pages \ -H Authorization: Bearer $WIKI_TOKEN \ -H Content-Type: application/json \ -d $payload \ -o $response_file \ -w %{http_code}) echo $http_code }这里用了jq来构造JSON比手动拼接字符串安全得多。--rawfile参数直接把文件内容读进来作为字符串省去了自己处理换行和转义的麻烦。如果服务器上没有jq可以用python3 -c替代但jq更轻量。4.5 日志内容写入的格式优化日志贴进Wiki后如果只是纯文本阅读体验很差。我做了几层优化。第一层是加时间戳标记在内容开头插入一行 日志采集时间$(date %Y-%m-%d %H:%M:%S)方便追溯。第二层是分段如果日志里有明显的分隔符比如就替换成Markdown的分割线。第三层是关键词高亮把ERROR替换成**ERROR**WARN替换成*WARN*让重点一眼可见。这些替换都用sed完成注意顺序先做高亮再做脱敏最后包装代码块。顺序错了会导致高亮标记被脱敏规则误伤。4.6 执行结果验证与日志记录脚本执行完不能只看退出码还要验证页面确实创建成功了。我的做法是创建完成后再查一次页面确认内容长度和预期一致。如果长度差太多说明内容可能在传输过程中被截断了。同时脚本自己也要记日志记录每次执行的时间、标题、状态码、内容行数。这个日志文件放在/var/log/wiki_auto_create.log方便后续审计和排查。日志格式用TSV方便用awk分析2025-01-15 10:30:00 nginx-error-20250115-01 201 487 2025-01-15 11:00:00 nginx-error-20250115-02 201 5125. 常见问题与排查技巧实录5.1 认证失败的三种典型表现认证问题是最常见的表现却各不相同。第一种是401 Unauthorized说明Token无效或过期需要重新生成。第二种是403 ForbiddenToken有效但权限不够可能是空间权限配置问题。第三种最隐蔽返回200但页面没创建成功响应体里其实是个错误信息。这种情况通常是Token对应的用户没有创建页面的权限但API设计成了返回200加错误码。排查方法先用curl手动发一次请求把完整响应体打印出来看。不要只看状态码响应体里的信息往往更关键。5.2 内容截断与编码问题日志内容贴进去发现少了一截或者中文变成乱码通常是编码问题。Wiki API一般要求UTF-8编码如果日志文件是GBK或者其他编码需要先转换iconv -f GBK -t UTF-8 $LOG_FILE /tmp/log_utf8.txt内容截断的另一个原因是curl的--data参数有长度限制超过一定大小会被截断。解决办法是用--data-binary file从文件读取而不是直接传字符串。另外检查Wiki系统本身有没有内容长度限制有些系统默认限制1MB需要在配置里调大。5.3 页面标题冲突的处理策略标题冲突的表现是返回409 Conflict或者虽然返回201但实际覆盖了已有页面。我的处理策略是先查询如果存在就在标题后面加序号最多重试10次。如果10次都冲突说明命名规则有问题需要人工介入。还有一种情况是标题里有特殊字符导致查询失败比如斜杠、问号、井号。这些字符在URL里有特殊含义必须编码。我前面给的urlencode函数能处理大部分情况但斜杠需要特别注意有些Wiki系统不允许标题里出现斜杠。5.4 网络超时与重试机制网络不稳定的时候curl可能会超时。默认超时时间太长脚本会卡住。我一般设置--connect-timeout 10 --max-time 30连接超过10秒或者总时间超过30秒就放弃。配合重试机制for i in 1 2 3; do http_code$(create_page $TITLE $CONTENT_FILE) if [ $http_code 201 ] || [ $http_code 200 ]; then break fi sleep $((i * 5)) done重试间隔递增避免给服务器造成压力。三次都失败就放弃记录错误日志等人工处理。5.5 常见问题速查表问题现象可能原因排查方法解决方案401 UnauthorizedToken无效或过期检查Token是否复制完整重新生成Token403 Forbidden权限不足确认用户对目标空间有写权限联系管理员开通权限409 Conflict页面标题已存在查询同名页面标题加序号或时间戳内容乱码编码不匹配file -i查看文件编码用iconv转UTF-8内容截断请求体过大检查内容长度用--data-binary从文件读取连接超时网络不通或服务器慢curl -v查看连接过程增加超时时间并重试页面创建成功但内容为空内容变量未正确传递打印payload检查用jq构造JSON避免转义问题提示这张表建议打印出来贴在工位上遇到问题先对照排查能省不少时间。5.6 几个容易忽略的细节第一个细节是临时文件的清理。脚本里用了好几个临时文件如果中途退出没清理时间长了会占满/tmp。我一般在脚本开头用trap cleanup EXIT注册清理函数不管正常退出还是异常退出都会执行。第二个细节是并发问题。如果多个脚本实例同时运行可能会创建同名页面。解决办法是用文件锁exec 200/var/lock/wiki_auto_create.lock flock -n 200 || { echo 另一个实例正在运行; exit 1; }这样同一时间只有一个实例能执行避免冲突。第三个细节是日志轮转。脚本自己的日志文件也要定期清理否则一年下来能攒几百兆。可以用logrotate配置或者脚本里判断文件大小超过10MB就归档。6. 脚本的扩展与日常维护建议6.1 从单一日志到多服务支持最初的脚本只处理nginx日志后来慢慢扩展到MySQL慢查询日志、应用错误日志、系统日志等。扩展的方式很简单把日志路径和过滤规则做成配置脚本根据参数选择不同的配置。比如case $SERVICE in nginx) LOG_FILE/var/log/nginx/error.log; FILTERERROR|WARN ;; mysql) LOG_FILE/var/log/mysql/slow.log; FILTERQuery_time ;; app) LOG_FILE/var/log/app/error.log; FILTERException|Error ;; esac这样一套脚本能覆盖大部分场景维护成本也低。6.2 定时执行与事件触发定时执行用cron最简单比如每天早上8点自动把前一天的日志归档到Wiki0 8 * * * /opt/scripts/wiki_auto_create.sh nginx /var/log/wiki_cron.log 21事件触发可以用inotifywait监控日志文件变化一旦有新内容就触发脚本。但这种方式要小心日志写入频繁的时候会触发太多次建议加个冷却时间比如5分钟内只执行一次。6.3 内容质量的持续优化脚本跑起来容易跑好难。我持续优化的几个方向一是过滤规则越来越精细从最初的只筛ERROR到后来按模块、按关键字、按时间范围多维过滤二是脱敏规则越来越完善除了IP和邮箱还加了手机号、身份证号、内部项目代号等三是格式越来越友好加了目录、摘要、相关链接等元素。这些优化没有终点每次遇到新的日志类型或者新的展示需求就迭代一版。关键是保持脚本的可读性和可维护性不要为了加功能把代码写得一团糟。6.4 团队协作中的注意事项如果脚本要给团队其他人用有几件事必须做。第一是写清楚README说明依赖、配置方法、使用示例。第二是参数校验要做足别人传错参数时要给出明确的错误提示而不是默默失败。第三是敏感信息处理要统一不能每个人用自己的脱敏规则否则容易漏掉。第四是版本管理用git管理脚本每次修改都提交方便回溯。我见过太多团队因为脚本没有版本管理改出问题后找不到之前的版本只能凭记忆重写。这种坑完全可以通过规范流程避免。6.5 性能与资源占用的平衡脚本本身很轻量但如果日志文件特别大读取和过滤会消耗不少CPU和内存。我的经验是超过100MB的日志文件不要直接grep先用split切成小块或者用awk流式处理。另外curl传输大内容时考虑用gzip压缩请求体能显著减少网络传输时间。还有一个容易被忽略的点是Wiki服务器的压力。如果脚本频繁创建页面可能会触发服务器的限流机制。建议在脚本里加个简单的速率控制比如每次请求间隔1秒避免给服务器造成负担。6.6 后续可以尝试的改进方向目前这套方案已经能满足日常需求但还有几个方向值得探索。一是接入消息通知页面创建成功后自动发个提醒到工作群方便团队成员及时查看。二是增加内容分析用简单的规则统计日志里的错误类型分布生成一个摘要放在页面顶部。三是支持模板不同类型的日志用不同的页面模板展示效果更专业。这些改进不需要一次性做完可以按需逐步添加。关键是先把核心流程跑通再考虑锦上添花。我在实际使用中最大的体会是自动化脚本的价值不在于功能多强大而在于稳定可靠、长期可用。一个每天都能正常运行的简单脚本比一个功能花哨但三天两头出问题的复杂系统有用得多。