资讯动态

Shell脚本从入门到实战:自动化运维与日志分析核心技巧

发布时间:2026/8/26 6:40:03 来源:尧图企业网站定制
1. 从“黑窗口”到自动化利器为什么你需要掌握Shell脚本如果你在命令行里敲过几次ls、cd或者mkdir那么恭喜你你已经踏入了Shell的世界。但如果你还在手动重复执行一系列命令或者被“无法将‘xxx’项识别为 cmdlet、函数、脚本文件或可运行程序的名称”这类错误提示搞得焦头烂额那么是时候系统性地学习Shell脚本了。Shell脚本远不止是命令的简单堆叠它是一门强大的胶水语言能将零散的命令、工具和系统调用粘合起来构建出自动化的工作流。无论是批量重命名文件、监控服务器日志、自动部署应用还是处理日常的繁琐数据一个几十行的脚本可能就能省下你几个小时甚至几天的手动劳动。这份笔记就是我结合多年运维和开发经验从最基础的语法到实际项目中的高级技巧为你梳理的一条从入门到精通的实战路径。无论你是刚接触Linux的新手还是想提升自动化效率的老兵这里都有你需要的干货。2. 脚本学习笔记的整体设计与核心思路学习Shell脚本切忌一上来就埋头啃厚厚的语法手册。我的核心思路是“问题驱动场景切入”。我们不是为了学语法而学语法而是为了解决实际问题。整个学习路径可以划分为四个阶段环境与基础认知、核心语法与流程控制、高级特性与调试技巧、实战项目与最佳实践。每个阶段都围绕具体的、你可能马上就会遇到的场景展开。例如学习变量不是为了记住$VAR这个符号而是为了理解如何在脚本中动态地保存一个文件名或一个端口号学习循环不是为了写for i in *而是为了批量处理当前目录下的所有日志文件。这种以终为始的学习方法能让你始终保持动力每学一点都能立刻看到效果。2.1 环境准备你的第一个脚本与常见“拦路虎”在写下第一行代码之前确保你的“战场”准备就绪。这不仅仅是打开一个终端那么简单。选择你的Shell虽然bash是绝大多数Linux发行版和macOS的默认Shell也是事实上的标准但知道它的存在和变体很重要。你可以通过echo $SHELL或echo $0来查看当前使用的Shell。对于学习而言坚持使用bash即可它的功能最全资料也最多。编辑器选择不要用Windows记事本它会在行尾添加不可见的字符导致脚本在Linux下执行出错。推荐使用VS Code、Vim、或Sublime Text等专业的文本编辑器它们能提供语法高亮大大提升编写体验。第一个脚本与执行权限创建一个名为hello.sh的文件内容如下#!/bin/bash # 这是一个注释 echo “Hello, Shell World!”第一行#!/bin/bash称为shebang它告诉系统使用哪个解释器来执行这个脚本。接下来是关键一步赋予脚本执行权限。chmod x hello.sh然后你可以通过./hello.sh来运行它。如果看到“Hello, Shell World!”恭喜你的第一个脚本成功了绕开第一个大坑“命令未找到”这是新手最常遇到的错误在相关热搜词里也高频出现比如“npm : 无法将‘npm’项识别为 cmdlet、函数、脚本文件或可运行程序的名称”。这个错误通常有两个原因命令确实未安装对于npm、adb这类工具你需要先安装它们。在Ubuntu/Debian上可能是sudo apt install npm android-tools-adb在CentOS/RHEL上是sudo yum install npm android-tools。命令不在PATH环境变量中系统只在PATH变量指定的目录列表里寻找可执行文件。你可以用echo $PATH查看当前路径。如果命令已安装但不在PATH中你有两种选择一是使用命令的绝对路径如/usr/local/bin/npm二是将命令所在目录添加到PATH中临时添加export PATH$PATH:/your/tool/path永久添加需要修改~/.bashrc或~/.bash_profile文件。对于“adb shell devices”显示“error: device not found”这通常意味着1) 安卓设备未通过USB连接或未开启USB调试模式2) 电脑上缺少对应的USB驱动程序尤其在Windows上。你需要依次检查这些环节。2.2 学习路径规划从基础语法到项目思维一个系统的学习路径能让你事半功倍。我将其分解为以下几个模块你可以按顺序攻克变量与参数理解本地变量、环境变量、特殊变量如$0,$1,$#,$?的区别和使用。这是脚本动态化的基础。条件判断掌握if...then...elif...else...fi结构以及test命令和它的方括号简写[ ]、双括号[[ ]]。学会判断文件是否存在、字符串是否相等、数值比较等。流程控制核心是循环。for循环用于遍历列表文件、数字、字符串while和until循环用于条件循环。这是实现批量操作的关键。函数将重复的代码块封装成函数提高脚本的复用性和可读性。理解函数如何定义、调用以及参数传递和返回值通过$?或全局变量。输入输出除了echo还要掌握read命令获取用户输入以及输入/输出重定向,,,21和管道|。文本处理三剑客grep搜索、sed流编辑、awk文本分析。这是Shell脚本处理结构化文本数据的超级武器。信号处理与调试让脚本更健壮学会使用trap命令处理中断信号如CtrlC以及使用set -x、set -e等选项进行脚本调试。实战项目整合将以上所有知识点融入一个完整的项目例如自动备份脚本、日志分析报告生成器、服务监控与告警脚本。3. 核心语法精讲与避坑指南掌握了整体框架我们来深入每一个核心语法点并附上我踩过的坑和总结的技巧。3.1 变量不仅仅是存储更是状态传递变量是脚本的“记忆单元”。Shell变量赋值时等号两边不能有空格这是第一个语法坑。# 正确 name“John” # 错误会尝试将“name”作为命令执行 name “John”引用变量时使用$符号为了明确变量边界尤其在拼接字符串时建议用${}包裹变量名。file“log” echo “${file}1.txt” # 输出 log1.txt echo “$file1.txt” # 尝试输出变量file1的值但file1未定义可能输出空特殊变量是Shell脚本的魔法所在它们由Shell自动设置$0: 脚本名称本身。$1, $2, ... $9: 脚本的第1到第9个参数。$#: 传递给脚本的参数个数。$和$*: 都代表所有参数。但在双引号内“$”是每个参数作为独立单词推荐“$*”是所有参数合并成一个单词。$?: 上一个命令的退出状态码。0通常表示成功非0表示失败。这是判断命令是否执行成功的关键。$$: 当前Shell进程的PID。注意在脚本中获取命令输出结果通常使用命令替换即反引号 或$()。$()是更现代且推荐的方式因为它支持嵌套。current_date$(date %Y-%m-%d) file_countls -l | grep “^-” | wc -l3.2 条件判断让你的脚本学会“思考”条件判断是脚本逻辑的核心。Shell使用test命令或其别名[ ]来进行判断。记住[后面和]前面必须有空格# 判断文件是否存在 if [ -f “/path/to/file” ]; then echo “File exists.” fi # 字符串比较 if [ “$USER” “root” ]; then echo “You are root.” fi # 数值比较使用 -eq, -ne, -gt, -lt, -ge, -le if [ $count -gt 10 ]; then echo “Count is greater than 10.” fi对于更复杂的算术比较和模式匹配推荐使用双括号[[ ]]它功能更强大且能避免很多引号相关的陷阱。# 使用 [[ ]] 进行模式匹配 if [[ “$filename” *.log ]]; then echo “This is a log file.” fi # 使用 [[ ]] 进行正则表达式匹配 if [[ “$email” ~ ^[a-zA-Z0-9._%-][a-zA-Z0-9.-]\.[a-zA-Z]{2,}$ ]]; then echo “Valid email.” fi常见坑点在判断变量时如果变量可能为空一定要用双引号引起来否则会引发语法错误。# 危险如果$name为空条件会变成 [ “John” ]语法错误 if [ $name “John” ]; then ... fi # 安全双引号确保即使为空也是一个合法的空字符串参数 if [ “$name” “John” ]; then ... fi3.3 循环与流程控制批量操作的发动机for循环是最常用的循环用于遍历一个已知的列表。# 遍历当前目录下所有 .txt 文件 for file in *.txt; do echo “Processing $file” # 可以对 $file 进行操作比如 cp “$file” “backup/$file.bak” done # 遍历数字序列 for i in {1..5}; do echo “Number $i” done # C语言风格的for循环适用于bash for ((i0; i10; i)); do echo “i is $i” donewhile循环在条件为真时持续执行。常用于读取文件内容或等待某个条件满足。# 逐行读取文件 while IFS read -r line; do echo “Line: $line” done “input.txt” # 注意IFS 防止行首尾空格被修剪-r 防止反斜杠转义。 # 等待某个进程结束 while ps -p $pid /dev/null; do sleep 1 echo “Process $pid is still running...” doneuntil循环与while相反条件为假时执行直到条件为真。使用场景相对较少。count0 until [ $count -ge 5 ]; do echo “Count is $count” ((count)) done循环控制break用于跳出整个循环continue用于跳过本次循环剩余部分进入下一次循环。3.4 函数模块化与代码复用将一段逻辑封装成函数能让脚本结构清晰易于维护。# 函数定义 log_message() { local level“$1” # local关键字声明局部变量避免污染全局 local msg“$2” echo “[$(date ‘%Y-%m-%d %H:%M:%S’)] [$level] $msg” } # 函数调用 log_message “INFO” “Script started.” if some_operation; then log_message “SUCCESS” “Operation completed.” else log_message “ERROR” “Operation failed with code $?.” fi函数通过位置参数$1, $2...接收参数通过return返回一个整数状态码0-255或者通过echo输出结果再由调用者用命令替换捕获。get_file_size() { local file“$1” if [ -f “$file” ]; then stat -c%s “$file” # 输出文件大小 return 0 else return 1 fi } size$(get_file_size “/path/to/file”) if [ $? -eq 0 ]; then echo “File size is ${size} bytes.” fi4. 文本处理三剑客grep, sed, awk这是Shell脚本处理文本数据的核心工具链威力巨大。4.1 grep全局搜索正则表达式并打印用于在文件或标准输入中搜索匹配模式的行。grep “pattern” file.txt在文件中搜索。grep -r “pattern” /dir递归搜索目录。grep -i “pattern”忽略大小写。grep -v “pattern”反向搜索打印不匹配的行。grep -E “pattern”使用扩展正则表达式等同于egrep。grep -o “pattern”只输出匹配到的部分而不是整行。# 在nginx日志中查找404错误 grep “ 404 “ /var/log/nginx/access.log # 统计包含“ERROR”的行数 grep -c “ERROR” application.log4.2 sed流编辑器用于对文本流进行非交互式编辑查找、替换、删除、插入。sed ‘s/old/new/’ file将每行第一个old替换为new。sed ‘s/old/new/g’全局替换。sed ‘s/old/new/2’替换每行第二个匹配项。sed ‘/pattern/d’删除匹配pattern的行。sed -n ‘/pattern/p’只打印匹配的行类似grep。sed -i.bak ‘s/old/new/g’ file直接修改原文件并创建备份file.bak。# 将所有“foo”替换为“bar” sed ‘s/foo/bar/g’ input.txt # 删除所有空行 sed ‘/^$/d’ input.txt # 在包含“start”的行的下一行插入“inserted line” sed ‘/start/a\inserted line’ input.txt4.3 awk文本分析报告生成器一门强大的编程语言擅长处理按列字段组织的文本数据。基本结构是awk ‘pattern {action}’ file。默认按空格或制表符分割字段$1, $2, ...代表第1、2...列$0代表整行。NR当前处理的行号。NF当前行的字段数。FS输入字段分隔符默认为空格。OFS输出字段分隔符。# 打印 /etc/passwd 文件的第一列用户名和第三列用户ID awk -F: ‘{print $1, $3}’ /etc/passwd # 计算文件总行数 awk ‘END {print NR}’ file.txt # 对第二列数值求和假设第二列是数字 awk ‘{sum $2} END {print “Total:”, sum}’ data.txt # 条件处理打印第一列且第二列大于100的行 awk ‘$2 100 {print $1}’ data.txt实操心得对于复杂的文本处理awk常常是最终解决方案。它内置了字符串、算术运算和关联数组功能非常完整。当grep和sed感觉力不从心时考虑用awk。5. 脚本健壮性与调试技巧一个健壮的脚本应该能优雅地处理错误和用户中断。5.1 错误处理set命令与退出码set -e脚本中任何命令的退出状态码非零即失败则立即退出脚本。这是防止错误累积的利器。set -u遇到未定义的变量时视为错误并退出。可以避免因变量名拼写错误导致的诡异问题。set -o pipefail管道命令中只要有一个命令失败整个管道就视为失败。默认情况下管道只以最后一个命令的退出状态为准。 通常我会在脚本开头加上这三条构成“安全模式”#!/bin/bash set -euo pipefail但要注意有些命令的退出码非零是正常的比如grep没找到匹配项会返回1。这时可以用|| true来忽略grep “pattern” file.txt || true5.2 信号处理优雅地响应CtrlC使用trap命令可以捕获并处理信号。#!/bin/bash cleanup() { echo “捕获到中断信号正在清理临时文件...” rm -f /tmp/temp_*.$$ echo “清理完成退出。” exit 1 } # 捕获 SIGINT (CtrlC) 和 SIGTERM (kill命令) 信号 trap cleanup SIGINT SIGTERM echo “脚本开始运行按 CtrlC 中断...” # 模拟一个长时间运行的任务 sleep 30 echo “任务正常完成。”这样即使用户中断脚本也能执行必要的清理工作避免留下垃圾文件。5.3 调试技巧让问题无处遁形输出调试信息最简单直接在关键位置用echo打印变量值。使用set -x它会在执行每一行命令之前先打印出扩展后的命令变量已被替换。调试完成后用set x关闭。#!/bin/bash set -x # 开启调试 name“World” echo “Hello, $name” set x # 关闭调试使用bash -x script.sh直接以调试模式运行整个脚本。检查脚本语法使用bash -n script.sh它只检查语法而不执行对于排查语法错误很有用。6. 实战项目构建一个自动化日志分析报告脚本现在我们把所有知识点串联起来构建一个实用的脚本它定期分析Nginx访问日志生成一个包含PV页面浏览量、UV独立访客、状态码统计和热门URL的简单日报。6.1 需求分析与设计假设我们的Nginx日志格式为通用格式存储在/var/log/nginx/access.log。我们需要脚本实现以下功能指定要分析的日志文件路径和时间范围默认为昨天。计算总请求数PV。根据IP地址计算独立访客数UV。统计不同HTTP状态码如200, 404, 500的出现次数。找出访问量最高的前10个URL。将结果输出到一个格式清晰的文本报告中并附上生成时间。6.2 脚本实现与逐行解析我们将脚本命名为nginx_log_analyzer.sh。#!/bin/bash # nginx_log_analyzer.sh - 简易Nginx日志分析报告生成器 set -euo pipefail # 1. 定义变量与默认值 LOG_FILE“${1:-/var/log/nginx/access.log}” # 第一个参数为日志文件默认使用标准路径 REPORT_DATE“${2:-$(date -d ‘yesterday’ %Y%m%d)}” # 第二个参数为报告日期默认昨天 REPORT_DIR“./reports” REPORT_FILE“${REPORT_DIR}/nginx_report_${REPORT_DATE}.txt” # 2. 检查日志文件是否存在 if [ ! -f “$LOG_FILE” ]; then echo “错误日志文件 $LOG_FILE 不存在” 2 exit 1 fi # 3. 创建报告目录 mkdir -p “$REPORT_DIR” # 4. 定义临时文件使用进程ID保证唯一性 TEMP_PV“/tmp/pv_$$.tmp” TEMP_IP“/tmp/ip_$$.tmp” TEMP_STATUS“/tmp/status_$$.tmp” TEMP_URL“/tmp/url_$$.tmp” # 5. 清理函数 cleanup() { rm -f “$TEMP_PV” “$TEMP_IP” “$TEMP_STATUS” “$TEMP_URL” echo “[$(date ‘%Y-%m-%d %H:%M:%S’)] 临时文件已清理。” “$REPORT_FILE” } trap cleanup EXIT # 脚本正常或异常退出时都执行清理 # 6. 生成报告头 { echo “” echo “ Nginx 访问日志分析报告” echo “ 分析日期$(date -d “$REPORT_DATE” ‘%Y-%m-%d’)” echo “ 日志文件$LOG_FILE” echo “ 生成时间$(date ‘%Y-%m-%d %H:%M:%S’)” echo “” echo } “$REPORT_FILE” # 7. 核心分析逻辑 echo “正在分析日志请稍候...” # 7.1 计算总PV假设日志每行一个请求 pv_count$(wc -l “$LOG_FILE”) echo “1. 总请求数 (PV): $pv_count” “$REPORT_FILE” # 7.2 计算UV按第一列IP地址去重 # 使用awk提取IPsort去重wc计数 uv_count$(awk ‘{print $1}’ “$LOG_FILE” | sort -u | wc -l) echo “2. 独立访客数 (UV): $uv_count” “$REPORT_FILE” # 7.3 统计状态码分布假设状态码在第9列 echo “3. HTTP 状态码分布” “$REPORT_FILE” awk ‘{status$9; if (status“”) status“-”; count[status]} END {for (s in count) printf(” %s: %d 次\n“, s, count[s])}’ “$LOG_FILE” | sort -rn -k2 “$REPORT_FILE” # 7.4 找出热门URL假设URL在第7列 echo “$REPORT_FILE” echo “4. 访问量最高的前10个URL” “$REPORT_FILE” awk ‘{print $7}’ “$LOG_FILE” | sort | uniq -c | sort -rn | head -10 | awk ‘{printf(” %3d 次: %s\n“, $1, $2)}’ “$REPORT_FILE” # 8. 报告完成 echo “$REPORT_FILE” echo “报告已生成$REPORT_FILE” “$REPORT_FILE” echo “分析完成。报告保存在$REPORT_FILE”6.3 脚本使用与扩展基本使用# 分析默认日志文件/var/log/nginx/access.log昨天的数据 ./nginx_log_analyzer.sh # 分析指定日志文件 ./nginx_log_analyzer.sh /path/to/your/access.log # 分析指定日期的日志需要日志文件本身包含该日期数据脚本仅作标识 ./nginx_log_analyzer.sh /path/to/access.log 20231001扩展思路更精细的日期过滤当前脚本分析整个日志文件。可以结合grep或awk的内置时间函数只分析指定日期的行。输出HTML或邮件将echo输出重定向到HTML模板或者使用mail命令将报告发送到邮箱。性能分析分析响应时间如果日志包含$request_time找出慢请求。错误监控将404和500状态码的URL单独列出方便排查问题。定时任务通过crontab设置每天凌晨自动运行此脚本实现日报自动化。# 每天凌晨2点运行分析脚本 0 2 * * * /path/to/nginx_log_analyzer.sh /var/log/nginx_analysis.log 217. 进阶技巧与性能考量当脚本变得越来越复杂或者需要处理大量数据时就需要考虑一些进阶技巧和性能问题。7.1 数组与关联数组Bash支持索引数组和关联数组类似其他语言中的Map或Dictionary用于存储复杂数据。# 索引数组 fruits(“apple” “banana” “orange”) echo ${fruits[0]} # apple echo ${fruits[]} # 所有元素 for fruit in “${fruits[]}”; do echo “$fruit” done # 关联数组 (bash 4.0) declare -A user_ages user_ages[“Alice”]25 user_ages[“Bob”]30 echo “Alice is ${user_ages[“Alice”]} years old.” for name in “${!user_ages[]}”; do # ${!array[]} 获取所有键 echo “$name: ${user_ages[$name]}” done7.2 进程替换与子Shell命令替换$(command)用命令的输出结果替换该表达式。进程替换(command)和(command)将一个命令的输出/输入模拟成一个文件。# 比较两个目录的文件列表差异 diff (ls /dir1) (ls /dir2) # 将命令输出同时传给多个进程 tee (grep “error” errors.txt) (grep “warning” warnings.txt) input.log子Shell用()包裹的命令会在一个子Shell中运行其变量更改不会影响父Shell。(cd /some/dir some_command) # 执行完命令后当前目录不变7.3 性能优化少调用外部命令在循环中频繁调用外部命令如grep,sed,awk,wc是Shell脚本性能的主要瓶颈。一个黄金法则是尽量将文本一次性读入然后用管道交给一个强大的文本处理工具尤其是awk来完成所有工作。低效示例# 在循环中反复调用grep和wc for file in *.log; do count$(grep “ERROR” “$file” | wc -l) echo “$file: $count errors” done高效示例# 使用awk一次性处理所有文件内部计数 awk ‘FNR1 FILENAME!oldfile {if (oldfile!“”) print oldfile“: ”count“ errors”; count0; oldfileFILENAME} /ERROR/ {count} END {print oldfile“: ”count“ errors”}’ *.log虽然第二段awk代码看起来复杂但它只启动了一个awk进程处理大量文件时速度会快几个数量级。对于简单的任务权衡可读性和性能对于处理GB级别日志的任务性能优化至关重要。7.4 使用shellcheck进行静态检查shellcheck是一个极佳的Shell脚本静态分析工具它能检查出语法错误、不规范的写法以及潜在的陷阱。强烈建议在编写完脚本后使用它进行检查。# 安装 shellcheck (以Ubuntu为例) sudo apt install shellcheck # 检查你的脚本 shellcheck your_script.sh它会给出非常具体的改进建议是提升脚本质量和健壮性的好帮手。8. 常见问题排查与经验实录即使按照最佳实践编写脚本在实际运行中仍会遇到各种问题。这里记录了一些典型问题和我的解决思路。8.1 路径与空格问题问题脚本在某个目录下运行正常换到另一个目录或由cron执行时就报错“找不到文件或目录”。排查检查脚本中使用的文件路径是绝对路径还是相对路径。在cron或由其他脚本调用时当前工作目录可能不同。对于关键文件尽量使用绝对路径。文件名或目录名中包含空格。在Shell中空格是参数的分隔符。必须用引号将变量括起来。# 错误如果$filename是“my file.txt”这会被解析为两个参数 rm $filename # 正确始终用双引号引用变量 rm “$filename”8.2 权限问题问题执行脚本时提示“权限被拒绝”。排查脚本本身没有执行权限chmod x script.sh。脚本试图写入或读取一个当前用户没有权限的目录或文件。使用ls -l检查文件权限必要时使用sudo但需谨慎并考虑安全性。特别注意从Windows系统复制到Linux的脚本文件有时行尾符CRLF会导致解释器错误。可以使用dos2unix命令转换或者用sed -i ‘s/\r$//’ script.sh删除回车符。8.3 变量在管道或循环中“消失”问题在管道或while循环中修改的变量在循环外部看不到变化。原因管道中的每个命令以及用|连接的while循环默认都在一个子Shell中运行。子Shell中的变量修改不会影响父Shell。解决方案避免在管道中使用while读变量改用输入重定向。# 变量修改无效 count0 cat file.txt | while read line; do ((count)) done echo $count # 输出 0 # 有效方案使用输入重定向 count0 while read line; do ((count)) done file.txt echo $count # 输出正确的行数使用进程替换如果必须用管道count0 while read line; do ((count)) done (cat file.txt) # 进程替换 echo $count使用lastpipe选项bash 4.2在脚本开头shopt -s lastpipe使管道中最后一个命令在当前Shell执行。8.4 脚本在后台运行与输出处理问题脚本通过放入后台运行或者通过nohup启动但输出不知道去哪了或者脚本突然终止。经验始终重定向输出和错误流。nohup默认将输出重定向到nohup.out。# 将标准输出和错误都重定向到日志文件 nohup ./long_running.sh script.log 21 对于需要长时间运行的后台脚本考虑使用systemd服务或supervisor来管理它们能提供更好的进程监控、日志轮转和自动重启功能。在脚本内部对于可能长时间运行的任务可以定期输出状态信息或写入心跳日志方便监控。8.5 处理包含特殊字符的文件名问题文件名包含换行符、制表符或其他奇怪字符时脚本处理会出错。终极方案使用find命令的-print0选项和xargs -0或者while IFS read -r -d ‘’循环它们使用空字符\0作为分隔符这是唯一不能在文件名中出现的字符。# 安全地删除所有 .tmp 文件即使文件名包含空格或换行 find . -name ‘*.tmp’ -print0 | xargs -0 rm -f # 安全地遍历find找到的文件 find . -type f -name ‘*.log’ -print0 | while IFS read -r -d ‘’ file; do echo “Processing: $file” # 对”$file”进行操作 done学习Shell脚本是一个不断实践和积累的过程。从简单的文件备份到复杂的系统部署流水线它的应用场景几乎无限。我个人的体会是最好的学习方法就是遇到一个具体的、重复性的任务然后尝试用脚本来解决它。开始时可能脚本写得笨拙、冗长但随着你对工具链的熟悉和对问题的深入理解你会写出越来越优雅、高效的代码。记住一个优秀的Shell脚本不一定需要多么高深的技巧但一定是清晰、健壮、有良好错误处理的。最后善用man手册如man bash、man grep和网络资源但更重要的是动手去写去调试去解决真实的问题。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价