简介本资源是一份面向Linux运维工程师及Shell初学者的实用脚本速查手册聚焦日常高频运维场景的自动化提效需求。文档以PDF形式呈现共1个文件大小仅100KB轻量便携、开即可用涵盖日志过滤如ERROR/FATAL统计、服务健康检查多IP批量ping、旧文件清理90天阈值、目录备份压缩、远程文件传输scp、用户home目录校验、实时日志监控tailegrep等11类典型任务并附带完整可执行代码与注释说明。内容源自一线运维实践脚本均经实际验证兼顾基础语法规范与工程健壮性如错误码判断、黑洞输出、变量安全引用并包含服务器初始化配置、进程管理、时区同步等进阶操作。目前已有1165人学习下载适合希望快速掌握运维脚本编写逻辑、积累可复用代码片段、提升重复任务处理效率的技术人员。1. 这不是“shell脚本合集”而是一线Linux运维每天真正在敲、在改、在加日志、在修超时的14个生产级片段你有没有过这种经历凌晨两点收到告警登录服务器后手忙脚乱翻自己半年前写的check_disk.sh发现里面df -h | awk {print $5}没过滤Use%表头导致95%被当成字符串比大小阈值永远不触发又或者用find -mtime 90清日志结果某台数据库服务器上/var/log/pgsql/下的归档日志被误删主从同步直接断裂——这不是玄学是 shell 里最真实的「时间陷阱」和「边界错觉」。这份《Linux运维必备工作常用shell脚本.pdf》不是教学PPT它来自一个连续三年负责200台CentOS/Ubuntu混合集群的工程师的实操笔记所有脚本都带真实路径/home/soyoger/logs、真实变量名hosts_file/home/soyoger/ip_list、真实失败反馈echo $ip is faild.拼写都没改甚至保留了他写到一半的注释“# 待查”。它解决的不是“怎么写for循环”而是“怎么让ping批量检测在300台机器上不因DNS超时卡死”、“怎么让tar备份不因文件名含空格崩掉”、“怎么让useradd创建用户后密码能真正生效而非报passwd: Authentication token manipulation error”。如果你是刚转岗运维的开发、正被日志巡检压得喘不过气的初级工程师、或需要快速交付自动化方案的外包实施人员——这14个片段每一个都能今天下午就复制进你的/opt/scripts/目录跑起来且大概率比你现用的版本更稳。2. 日志过滤与服务健康检查从grep单行到带状态码的健壮探测链2.1 错误日志提取为什么-i ERROR不够必须加-E和^锚点原始脚本中这行看似简单grep -i ERROR error.log但生产环境里它会把WARNING: ERROR_HANDLER_INIT_FAILED、user_error_code1001、甚至error.log.bak全部捞出来。更危险的是当error.log是滚动日志如error.log.1.gzgrep默认不处理压缩文件导致漏报。正确做法是分层过滤# 步骤1解压并合并所有相关日志支持 .gz/.bz2 zcat /var/log/app/error.log*.gz 2/dev/null | \ cat /var/log/app/error.log - | \ # 步骤2用扩展正则精确匹配独立单词ERROR前后非字母数字 grep -E (^|[^a-zA-Z0-9])ERROR([^a-zA-Z0-9]|$) | \ # 步骤3去重并按时间倒序假设日志首字段为ISO时间 sort -u -k1,1r参数说明-E启用扩展正则避免grep -i ERROR\|FATAL\|CRITICAL写成多条命令(^|[^a-zA-Z0-9])确保ERROR前是行首或非单词字符sort -u -k1,1r中-k1,1r表示仅按第1字段时间倒序-u去重防同一错误刷屏2/dev/null屏蔽zcat: cant stat等警告避免污染结果流。2.2 服务健康检查ping只是第一道门必须叠加端口探测和响应超时原始脚本用ping -c1 $ip /dev/null判断存活但问题极多ICMP可能被防火墙拦截、主机禁ping但服务正常、网络抖动导致单次ping丢包。真正的健康检查必须分层#!/bin/bash # health_check.sh支持IP列表、端口、超时、重试的健壮探测 HOSTS_FILE/home/soyoger/ip_list # 格式192.168.1.10:22,192.168.1.11:8080 TIMEOUT3 RETRY2 while IFS read -r line; do [[ -z $line || $line ~ ^[[:space:]]*# ]] continue # 跳过空行和注释 ip_port(${line//:/ }) # 拆分IP和端口如192.168.1.10:22 → (192.168.1.10 22) ip${ip_port[0]} port${ip_port[1]:-22} # 默认22端口 # 阶段1ICMP探测快速筛掉彻底宕机的 if ! ping -c1 -W$TIMEOUT $ip /dev/null; then echo [$(date %H:%M:%S)] $ip: ICMP unreachable continue fi # 阶段2TCP端口探测核心服务验证 for ((i0; iRETRY; i)); do if timeout $TIMEOUT bash -c echo /dev/tcp/$ip/$port 2/dev/null; then echo [$(date %H:%M:%S)] $ip:$port OK break elif [[ $i -eq $((RETRY-1)) ]]; then echo [$(date %H:%M:%S)] $ip:$port FAILED after $RETRY attempts fi done done $HOSTS_FILE关键设计逻辑使用timeout命令强制限制bash -c echo /dev/tcp/...的执行时间避免因端口无响应卡死IFS read -r保证读取含空格的IP列表不被截断[[ -z $line || $line ~ ^[[:space:]]*# ]]同时跳过空行和以#开头的注释行端口默认值22通过${ip_port[1]:-22}实现比硬编码更灵活。2.3 日志实时监控tail -f 的致命缺陷与替代方案原始脚本用tail -fn0 /var/log/message | while read line监控但存在两个严重问题while read在管道中运行于子shell内部变量无法传递到外部tail -f在日志轮转logrotate时会丢失新文件内容需--followname配合--retry。生产级替代方案使用inotifywait#!/bin/bash # log_monitor.sh基于inotify的零丢失日志监控 LOG_PATH/var/log/messages ALERT_FILE/tmp/error_alert.log # 安装依赖Ubuntu/CentOS通用 if ! command -v inotifywait /dev/null; then echo inotify-tools not installed. Installing... if command -v apt-get /dev/null; then apt-get update apt-get install -y inotify-tools elif command -v yum /dev/null; then yum install -y inotify-tools fi fi # 创建alert文件并设置初始权限 touch $ALERT_FILE chmod 644 $ALERT_FILE # 监控日志文件变化包括轮转后的新文件 inotifywait -m -e move_self,create,modify $LOG_PATH --format %w%f | while read file; do # 检查是否为当前日志避免监控到 .1 .2.gz 等旧文件 if [[ $(basename $file) $(basename $LOG_PATH) ]]; then # 实时grep新内容-q静默-m1只匹配第一个即退出防阻塞 grep -q -E (error|fail|shut down|panic) $file \ tail -n1 $file | tee -a $ALERT_FILE fi done为什么比tail可靠inotifywait -m持续监听move_self事件捕获日志轮转如messages→messages.1create捕获新日志生成tail -n1只取最新一行配合tee -a追加到告警文件避免重复写入grep -q -m1确保每次只触发一次告警防止高频错误刷爆磁盘。3. 文件操作与备份find的mtime陷阱与tar的安全打包实践3.1 删除旧文件-mtime 90的时区偏差与安全删除协议原始脚本直接使用find /home/soyoger/logs -type f -mtime 90 -exec rm {} \;但find -mtime的计算基于「文件修改时间mtime距今多少个24小时周期」而非自然日。若日志在UTC时间23:59写入而服务器时区为CSTUTC8则find计算时会因时区转换产生1小时偏差导致本该保留的文件被误删。修正方案用-newermt指定绝对日期#!/bin/bash # safe_purge.sh按绝对日期安全清理 LOG_DIR/home/soyoger/logs RETENTION_DAYS90 # 计算90天前的日期格式YYYY-MM-DD CUTOFF_DATE$(date -d $RETENTION_DAYS days ago %Y-%m-%d) # 创建临时标记文件其mtime设为CUTOFF_DATE touch -d $CUTOFF_DATE /tmp/cutoff_marker # 查找修改时间早于标记文件的所有文件精确到秒 find $LOG_DIR -type f ! -newermt $CUTOFF_DATE -print0 | \ while IFS read -r -d file; do # 双重确认文件存在且非符号链接防误删软链目标 if [[ -f $file ! -L $file ]]; then echo Deleting: $file rm -f $file fi done rm -f /tmp/cutoff_marker参数深挖-newermt $CUTOFF_DATE比-mtime 90精确直接比较文件mtime与指定日期-print0read -d 处理含空格/换行符的文件名这是生产环境必备! -L $file排除符号链接避免rm -f误删链接指向的真实日志。3.2 备份目录tar的路径安全与增量备份策略原始脚本用tar cvf log_bak.rar ./log但存在三个风险log_bak.rar是错误后缀应为.tar.gz且未压缩./log相对路径在crontab中执行时可能因工作目录不同而失败无校验机制备份损坏无法感知。生产级备份脚本含校验与增量#!/bin/bash # backup_logs.sh带SHA256校验与增量备份 SOURCE_DIR/home/soyoger/logs BACKUP_ROOT/backup DATE$(date %Y%m%d_%H%M%S) FULL_BACKUP$BACKUP_ROOT/logs_full_$DATE.tar.gz INC_BACKUP$BACKUP_ROOT/logs_inc_$DATE.tar.gz LAST_FULL$(ls -t $BACKUP_ROOT/logs_full_*.tar.gz 2/dev/null | head -1) # 步骤1创建全量备份首次或每周日执行 if [[ -z $LAST_FULL ]] || [[ $(date %u) -eq 7 ]]; then tar -czf $FULL_BACKUP -C $(dirname $SOURCE_DIR) $(basename $SOURCE_DIR) \ sha256sum $FULL_BACKUP $FULL_BACKUP.sha256 echo Full backup created: $FULL_BACKUP else # 步骤2增量备份基于上次全量 tar -czf $INC_BACKUP -C $(dirname $SOURCE_DIR) \ --newer-mtime$(stat -c %y $LAST_FULL | cut -d -f1) \ $(basename $SOURCE_DIR) \ sha256sum $INC_BACKUP $INC_BACKUP.sha256 echo Incremental backup created: $INC_BACKUP fi # 步骤3清理7天前的备份保留全量最近3个增量 find $BACKUP_ROOT -name logs_*_[0-9]*.tar.gz -mtime 7 -delete find $BACKUP_ROOT -name logs_*_[0-9]*.tar.gz.sha256 -mtime 7 -delete关键加固点-C $(dirname $SOURCE_DIR)将工作目录切到父目录再用$(basename $SOURCE_DIR)指定相对路径确保tar输出不带绝对路径前缀--newer-mtime获取上次全量备份时间戳实现精准增量sha256sum生成校验文件恢复前可sha256sum -c backup.tar.gz.sha256验证完整性find ... -mtime 7 -delete清理策略独立于备份逻辑避免误删。3.3 避坑文件操作中5个血泪教训现象1for file in *.txt循环在无匹配文件时file变量值为字面量*.txt导致mv *.txt .bak报错→原因Bash默认不启用nullglob通配符无匹配时原样返回→解决在脚本开头加shopt -s nullglob或改用find . -maxdepth 1 -name *.txt -print0 | while IFS read -r -d file现象2find ... -exec rm {} \;删除含空格的文件名时报错rm: cannot remove ‘file name.txt’: No such file or directory→原因\;分隔符使rm接收多个参数空格被当成分隔符→解决用-exec rm {} 批量传参或-print0 | xargs -0 rm现象3tar cvf archive.tar /var/log打包后解压路径为/var/log破坏目标服务器目录结构→原因绝对路径打包会保留根目录→解决cd / tar cvf archive.tar var/log或tar -C / -cvf archive.tar var/log现象4cp -r /source/* /dest/时若/source/下有隐藏文件.env会被忽略→原因*不匹配以.开头的文件→解决shopt -s dotglob启用点文件匹配或cp -r /source/. /dest/现象5echo log: $(date) /var/log/myapp.log在高并发下出现日志错乱如两行合并为一行→原因重定向在内核层面非原子操作多进程写入竞争→解决用logger -t myapp log message交由syslog守护进程序列化写入。4. 用户与进程管理从useradd到密码策略的完整闭环4.1 自动化创建用户为什么echo 123456 | passwd --stdin在RHEL8失效原始脚本中echo 123456 | passwd --stdin ${name}但在RHEL8/CentOS8系统中--stdin选项默认被禁用需PAM模块pam_pwquality支持且明文密码硬编码存在审计风险。安全替代方案使用openssl生成随机密码并加密#!/bin/bash # create_user_secure.sh符合NIST密码策略的用户创建 USERNAME$1 if [[ -z $USERNAME ]]; then echo Usage: $0 username exit 1 fi # 检查用户是否存在兼容不同系统 if id $USERNAME /dev/null; then echo User $USERNAME already exists. exit 1 fi # 生成强密码12位含大小写字母数字符号排除易混淆字符 PASSWORD$(openssl rand -base64 12 | tr -d / | fold -w12 | head -1 | \ sed s/[0OIl1]//g | cut -c1-12) # 创建用户指定shell、家目录、组 useradd -m -s /bin/bash -c Auto-created user $USERNAME # 设置密码兼容RHEL7/8/9及Ubuntu if command -v chpasswd /dev/null; then echo $USERNAME:$PASSWORD | chpasswd elif command -v passwd /dev/null; then echo $PASSWORD | passwd $USERNAME --stdin 2/dev/null || \ (echo $USERNAME:$PASSWORD | chpasswd) fi # 强制密码下次登录修改 chage -d 0 $USERNAME # 记录到安全位置非世界可读 echo $(date): $USERNAME created with password: $PASSWORD /var/log/user_creation.log chmod 600 /var/log/user_creation.log密码策略细节openssl rand -base64 12生成高强度随机字节tr -d /去除Base64填充字符sed s/[0OIl1]//g移除数字0、大写O、小写l、数字1、大写I防视觉混淆chage -d 0设置密码过期时间为0强制用户首次登录改密。4.2 检查并Kill进程grep -v grep 的替代与优雅终止原始脚本用ps -ef | grep xxx | grep -v grep | awk {print $2} | xargs -I{} kill {}但grep -v grep是经典hack不可靠若进程名含grep则误杀且kill粗暴终止可能丢失数据。专业方案使用pgrep/pkill 信号分级#!/bin/bash # manage_process.sh安全进程管理 PROCESS_NAMEnginx TIMEOUT10 # 步骤1查找进程PID精确匹配排除自身 PIDS$(pgrep -f ^$PROCESS_NAME 2/dev/null) if [[ -z $PIDS ]]; then echo No process found for $PROCESS_NAME exit 0 fi echo Found PIDs: $PIDS # 步骤2发送SIGTERM优雅终止等待10秒 echo Sending SIGTERM to $PIDS... kill -TERM $PIDS # 步骤3检查是否退出未退出则发送SIGKILL for ((i0; iTIMEOUT; i)); do if ! pgrep -f ^$PROCESS_NAME /dev/null; then echo Process $PROCESS_NAME terminated gracefully. exit 0 fi sleep 1 done echo Process still running after $TIMEOUT seconds. Sending SIGKILL... kill -KILL $PIDS为什么更可靠pgrep -f ^$PROCESS_NAME用^锚定行首确保匹配完整命令行如nginx: master process避免匹配到systemctl status nginxkill -TERM先发终止信号给进程清理资源的机会循环检查pgrep结果比sleep 10 kill -KILL更精准。4.3 用户Home目录检查从遍历/home到解析/etc/passwd的深度校验原始脚本cd /home for dir in *; do is_exist$(grep -c /home/${dir} /etc/passwd) # ... done但此方法有漏洞若用户家目录不在/home如/opt/users/xxx则漏检若/home下有非用户目录如lostfound会误报grep -c统计次数但无法区分主目录与shell字段。精准校验方案解析passwd字段#!/bin/bash # check_home_dirs.sh基于/etc/passwd的权威校验 HOME_BASE/home # 提取所有有效用户的家目录排除nologin/shell用户 VALID_HOMES$(awk -F: $7 !~ /(nologin|false)$/ $6 ~ /^\/home\// {print $6} /etc/passwd | sort -u) echo Valid user home directories echo $VALID_HOMES # 检查/home下是否存在对应目录 echo -e \n Orphaned directories in $HOME_BASE comm -23 (ls -1 $HOME_BASE 2/dev/null | sort) (echo $VALID_HOMES | xargs -n1 dirname | sort -u) | \ while read orphan; do if [[ $orphan ! lostfound $orphan ! . $orphan ! .. ]]; then echo Orphaned: $HOME_BASE/$orphan # 可选自动清理或告警 # rmdir $HOME_BASE/$orphan 2/dev/null || echo Non-empty: $HOME_BASE/$orphan fi done echo -e \n Missing home directories echo $VALID_HOMES | while read home; do if [[ -n $home ! -d $home ]]; then echo Missing: $home # 可选自动创建 # mkdir -p $home chown $(basename $home):$(basename $home) $home fi done技术要点awk -F: $7 !~ /(nologin|false)$/过滤掉shell为/sbin/nologin的系统用户comm -23比较两个排序后的列表输出仅在第一个列表ls结果中出现的项即孤儿目录xargs -n1 dirname将/home/user1提取为/home用于与ls -1 /home结果对齐。5. 系统初始化与资源监控从配置固化到实时指标采集5.1 服务器初始化SELinux与防火墙的条件化配置原始脚本中# 禁⽤selinux# 待查 # 关闭防⽕墙# 待查根据⾃⼰操作系统设置这种“待查”在生产环境是定时炸弹。必须根据发行版和安全策略动态决策。智能初始化脚本自动适配RHEL/Ubuntu#!/bin/bash # init_server.sh发行版自适应初始化 DISTRO$(awk -F /^ID/ {print $2} /etc/os-release | tr -d ) ARCH$(uname -m) echo Detected distro: $DISTRO, arch: $ARCH # 步骤1SELinux配置仅RHEL/CentOS/Fedora if [[ $DISTRO ~ ^(rhel|centos|fedora)$ ]]; then echo Configuring SELinux... # 检查是否已禁用 if sestatus | grep disabled /dev/null; then echo SELinux already disabled. else # 临时禁用 setenforce 0 # 永久禁用修改配置文件 sed -i s/^SELINUX.*/SELINUXdisabled/ /etc/selinux/config echo SELinux disabled. Reboot required for permanent effect. fi fi # 步骤2防火墙配置RHEL用firewalldUbuntu用ufw if [[ $DISTRO ubuntu ]]; then echo Configuring UFW... if ! ufw status | grep Status: active /dev/null; then ufw --force enable ufw default deny incoming ufw allow OpenSSH fi elif [[ $DISTRO ~ ^(rhel|centos|fedora)$ ]]; then echo Configuring firewalld... if ! systemctl is-active --quiet firewalld; then systemctl enable firewalld systemctl start firewalld fi firewall-cmd --permanent --add-servicessh firewall-cmd --reload fi # 步骤3历史命令时间戳全局生效 if ! grep -q HISTTIMEFORMAT /etc/bash.bashrc; then echo export HISTTIMEFORMAT%F %T /etc/bash.bashrc echo HISTTIMEFORMAT added to /etc/bash.bashrc fi适配逻辑awk -F /^ID/ {print $2}从/etc/os-release提取标准发行版IDsestatus检查SELinux状态避免重复操作ufw --force enable强制启用UFWfirewall-cmd --reload确保规则生效修改/etc/bash.bashrcUbuntu或/etc/bashrcRHEL确保所有用户生效。5.2 一键资源监控修复原始脚本中的vmstat字段错位与单位混乱原始脚本函数function cup(){ util$(vmstat | awk {if(NR3)print $13$14}) iowait$(vmstat | awk {if(NR3)print $16}) echo CPU-使⽤率${util}%,等待磁盘IO相应使⽤率${iostat}%. }但vmstat输出字段随版本变化RHEL7中CPU字段为$14,$15,$16,$17us,sy,id,wa而脚本误用$13,$14且iostat变量名拼错为iowait导致输出iostat%。跨版本兼容的监控函数#!/bin/bash # monitor_resources.sh准确采集CPU/内存/磁盘 # CPU使用率ussywa排除id get_cpu_usage() { # 获取vmstat标题行定位us/sy/wa列号 local header$(vmstat 1 1 | head -1) local us_col$(echo $header | awk {for(i1;iNF;i) if($ius) print i}) local sy_col$(echo $header | awk {for(i1;iNF;i) if($isy) print i}) local wa_col$(echo $header | awk {for(i1;iNF;i) if($iwa) print i}) # 获取第二行数据实际数值行 local data$(vmstat 1 1 | tail -1) local us$(echo $data | awk -v col$us_col {print $col}) local sy$(echo $data | awk -v col$sy_col {print $col}) local wa$(echo $data | awk -v col$wa_col {print $col}) echo $((us sy wa)) } # 内存使用率单位统一为MB get_memory_usage() { local total$(free -m | awk NR2{print $2}) local used$(free -m | awk NR2{print $3}) local avail$(free -m | awk NR2{print $7}) echo total:$total,used:$used,available:$avail } # 磁盘使用率取根分区 get_disk_usage() { df -h / | awk NR2{print $5} } # 主函数 monitor() { echo System Resource Monitor ($(date)) echo CPU Usage: $(get_cpu_usage)% echo Memory: $(get_memory_usage) echo Root Disk: $(get_disk_usage) used echo Load Average: $(uptime | awk -Fload average: {print $2}) } monitor字段定位原理vmstat 1 1生成单行输出head -1提取标题awk动态查找us/sy/wa字段索引free -m统一单位为MB避免free -g在小内存机器上显示0df -h /明确指定根分区避免df -h输出多行需解析。5.3 避坑系统配置中3个隐蔽但致命的错误现象1ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime在容器中失效→原因容器内/etc/localtime通常是挂载的tmpfs符号链接被覆盖→解决在Dockerfile中用ENV TZAsia/Shanghai或在宿主机用timedatectl set-timezone Asia/Shanghai现象2ntpdate ntp1.aliyun.com在systemd系统中被废弃且未处理时钟跳跃→原因ntpdate是单次同步systemd-timesyncd或chronyd才是现代服务大时间差会导致应用异常→解决启用systemd-timesyncd并配置FallbackNTPntp1.aliyun.com或用chronyd -q server ntp1.aliyun.com iburst现象3echo TMOUT600 /etc/profile导致所有用户会话600秒无操作自动退出但root用户可能因此锁死→原因/etc/profile对所有shell生效包括root的维护会话→解决改为echo readonly TMOUT600 /etc/profile.d/tmout.sh并添加unset TMOUT到root的~/.bashrc作为例外。6. 进阶技巧如何让这些脚本真正融入你的运维流水线6.1 脚本标准化从“能跑”到“可审计、可回滚、可监控”一线工程师的血泪经验一个脚本上线前必须回答三个问题——Q1它改了什么→ 所有变更操作useradd/rm/sed -i前加echo [DRYRUN] Would run: ..., 生产环境默认开启dry-run模式用-d参数关闭。Q2它失败了怎么办→ 每个关键步骤后加|| { echo ERROR at line $LINENO; exit 1; }并在脚本开头定义set -euo pipefail。Q3它被谁、何时、为何运行→ 所有脚本在开头记录调用栈echo [$(date)] STARTED by $(whoami) from $(pwd) via $(ps -o args $PPID) /var/log/scripts.log标准化模板copy-paste即可用#!/bin/bash # Standardized script template v1.0 set -euo pipefail # 严格错误处理 SCRIPT_NAME$(basename $0) LOG_FILE/var/log/scripts/${SCRIPT_NAME%.sh}.log DRYRUNtrue # 参数解析 while getopts dvh opt; do case $opt in d) DRYRUNfalse ;; v) echo $SCRIPT_NAME v1.0; exit 0 ;; h) echo Usage: $0 [-d] [-v] [-h]; exit 0 ;; esac done # 日志函数 log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* | tee -a $LOG_FILE; } log STARTED by $(whoami) from $(pwd) # 主逻辑示例安全删除 if [[ $DRYRUN true ]]; then log [DRYRUN] Would delete files older than 90 days in /home/soyoger/logs else log Deleting files... find /home/soyoger/logs -type f -mtime 90 -delete 21 | log fi log FINISHED6.2 集成到Cron与Ansible让脚本从手动执行升级为基础设施代码Cron最佳实践避免PATH问题原始crontab可能写成0 2 * * * /home/soyoger/scripts/purge_logs.sh但cron环境PATH极简通常只有/usr/bin:/bin导致find/date等命令找不到。正确写法显式声明PATH# 编辑 crontab -e SHELL/bin/bash PATH/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin 0 2 * * * /home/so p a hrefhttps://download.csdn.net/download/qq_36330643/61690615 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
阅读完成 · 觉得有帮助?