资讯动态

从零开始学 Linux(二):Shell 脚本入门与文本处理实战

发布时间:2026/10/19 5:02:35 来源:尧图企业网站定制
前言前篇都是手动敲单条命令当要重复执行几十上百条操作的时候一条条敲效率太低了。这时候就可以把命令按照逻辑写成 Shell 脚本一次性批量执行实现自动化。Shell 脚本不需要编译直接解释运行是运维自动化最常用的工具。一、脚本入门怎么写和执行脚本Shell 脚本本质就是一个文本文件第一行必须用#!/bin/bash指定用哪个解释器来执行它后面一行一条 Linux 命令就行。 比如最简单的脚本#!/bin/bash echo Hello, Shell!执行脚本有两种方式原理不一样直接用解释器调用bash ./test.sh这种方式不需要脚本有执行权限本质是 bash 程序帮你读这个脚本执行给脚本加执行权限之后直接跑chmod x test.sh然后./test.sh这种方式脚本自己就是可执行程序系统会根据第一行的解释器路径来执行保存为hello.sh运行方式bash hello.sh # 用 bash 解释器执行脚本 sh hello.sh # 用 sh 解释器执行脚本 chmod x hello.sh # 给脚本增加可执行权限 ./hello.sh # 以可执行文件方式运行脚本小技巧还有一种source ./test.sh的执行方式不会开子进程直接在当前终端环境执行一般用来加载配置文件。二、变量体系1. 变量基础定义变量的时候等号两边不能有空格这是新手最容易踩的坑name张三是对的name 张三会直接报错。 Shell 里的变量默认都是字符串类型不能直接做数学运算。变量值如果有空格要用引号包起来desc我是一个字符串。unset 变量名删除变量readonly 变量名声明只读变量不能改也不能删用export 变量名可以把变量升级成全局环境变量这样子脚本也能读到这个变量注意是父进程传子进程反过来不行。系统变量echo $PATH # 输出 PATH 环境变量 echo $HOME # 输出当前用户家目录 echo $USER # 输出当前用户名 echo $SHELL # 输出当前 Shell 路径自定义变量app_nameblog-service # 定义变量 app_name version1.0 # 定义变量 version注意等号两边不能有空格值有空格时用引号变量默认按字符串处理推荐用${变量名}引用只读变量readonly envprod # 定义只读变量 env不可修改撤销变量unset version # 删除变量 version导出环境变量export APP_ENVproduction # 将 APP_ENV 导出为环境变量2. 特殊位置变量这些变量是脚本运行时自动填充的写通用脚本的时候特别好用变量作用$0脚本自己的文件名$1~$9执行脚本时传的第 1 到第 9 个参数$#一共传了多少个参数$*所有参数当成一个整体字符串$所有参数每个都是独立的$?上一条命令的退出状态码0 是成功非 0 是失败这里$*和$的区别在循环里特别明显加双引号的时候$*是把所有参数拼成一个字符串循环只跑一次$是每个参数独立有几个参数跑几次循环。示例#!/usr/bin/env bash # 指定 bash 解释器 echo 脚本名$0 # 输出脚本名称 echo 第一个参数$1 # 输出第一个参数 echo 参数个数$# # 输出参数个数 echo 全部参数$ # 输出所有参数$*和$在加双引号时区别明显for arg in $; do # 遍历每个独立参数 echo arg: $arg # 输出当前参数 done # 循环结束三、运算、判断与流程控制1. 算术运算因为变量默认是字符串做数学计算要用专门的语法$((表达式))或者$[表达式]比如sum$((12*3))注意 Shell 默认只支持整数运算。a$((3 5)) # 计算 35结果赋给 a b$[10 * 2] # 计算 10*2结果赋给 b echo $a # 输出 a echo $b # 输出 bShell 原生一般只支持整数运算。浮点计算可用bc或awkecho 3.14 * 2 | bc # 用 bc 计算浮点乘法 awk BEGIN {print 3.14 * 2} # 用 awk 计算浮点乘法2. 条件判断判断用[ 条件 ]括号两边必须有空格常用的判断整数比较不能直接用大于小于要用-eq等于、-ne不等于、-gt大于、-lt小于文件判断-e文件存在、-f是普通文件、-d是目录、-r有读权限、-x有执行权限示例if [ -f /data/app/app.conf ]; then # 判断文件是否存在且为普通文件 echo 配置文件存在 # 条件成立时输出 fi # 结束 if if [ $1 -gt 10 ]; then # 判断第一个参数是否大于 10 echo 参数大于 10 # 条件成立时输出 fi # 结束 if逻辑组合[ -f app.log ] echo 日志存在 # 文件存在则输出提示 [ -d backup ] || mkdir backup # 目录不存在则创建 backup3. 流程控制if分支if [ 条件 ]; then ... elif [ 条件 ]; then ... else ... fiif [ $1 start ]; then # 如果第一个参数是 start echo 启动服务 # 输出启动服务 elif [ $1 stop ]; then # 否则如果第一个参数是 stop echo 停止服务 # 输出停止服务 else # 其他情况 echo 用法$0 {start|stop} # 输出用法提示 fi # 结束 ifcase多分支适合一个变量有多个固定取值的场景比堆 if 简洁每个分支用;;结束相当于 breakcase $1 in # 根据第一个参数匹配 start) # 匹配 start echo 启动服务 # 输出启动服务 ;; # 分支结束 stop) # 匹配 stop echo 停止服务 # 输出停止服务 ;; # 分支结束 restart) # 匹配 restart echo 重启服务 # 输出重启服务 ;; # 分支结束 *) # 默认分支 echo 用法$0 {start|stop|restart} # 输出用法 ;; # 分支结束 esac # 结束 casefor循环支持 C 语言风格for((i1;i100;i))也支持for i in 列表遍历集合for name in tom jerry lucy; do # 遍历列表中的每个名字 echo hello $name # 输出问候语 done # 循环结束 sum0 # 初始化 sum 为 0 for ((i 1; i 100; i)); do # 从 1 循环到 100 sum$(($sum $i)) # 累加 i 到 sum done # 循环结束 echo $sum # 输出累加结果while循环while [ 条件 ]; do ... done条件成立就一直循环i1 # 初始化 i 为 1 sum0 # 初始化 sum 为 0 while [ $i -le 100 ]; do # 当 i 小于等于 100 时循环 sum$(($sum $i)) # 累加 i 到 sum i$((i 1)) # i 自增 1 done # 循环结束 echo $sum # 输出累加结果read -p 提示文字 变量名脚本运行的时候接收用户从终端输入的内容while read -r line; do # 逐行读取文件内容 echo 读取到$line # 输出当前行 done app.log # 从 app.log 读取4. 自定义函数把重复的逻辑封装成函数提升复用性# 函数定义 function add() { echo $(( $1 $2 )) } # 调用函数参数直接跟在后面 result$(add 1 2)log_info() { # 定义 log_info 函数 echo [INFO] $(date %F %T) $* # 输出带时间戳的 INFO 日志 } # 函数结束 log_error() { # 定义 log_error 函数 echo [ERROR] $(date %F %T) $* 2 # 输出带时间戳的 ERROR 日志到标准错误 } # 函数结束 add() { # 定义 add 函数 local a$1 # 第一个参数赋给局部变量 a local b$2 # 第二个参数赋给局部变量 b echo $(($a $b)) # 输出两数之和 } # 函数结束 log_info 开始执行 # 调用 log_info log_error 出现异常 # 调用 log_error add 3 5 # 调用 add传入 3 和 5说明函数必须先定义后调用local定义局部变量return只能返回 0~255 的整数复杂返回值可以用echo输出再由调用方捕获四、文本处理两大利器1. cut简单列切割cut 就是按分隔符切列适合格式规整的文本cut -d 分隔符 -f 列号比如echo $PATH | cut -d : -f 3-就是取 PATH 里从第 3 个冒号开始的所有路径。缺点是分隔符只能是单个字符多个连续空格就处理不好。按列提取cut -d : -f 1,7 /etc/passwd # 以冒号分隔提取第 1 和第 7 列 echo $PATH | cut -d : -f 3- # 以冒号分隔提取第 3 列及之后所有列2. awk强大的文本分析工具awk 比 cut 灵活得多它逐行读文本自动按空格切分多列$0是整行$1是第一列$2是第二列还支持正则匹配、统计计算。 比如提取 passwd 文件里 root 用户的 Shellawk -F : /^root/{print $7} /etc/passwd这里-F :指定用冒号当分隔符/^root/是正则匹配以 root 开头的行{print $7}打印第 7 列。 还支持BEGIN块处理文本前先执行比如打印表头和END块所有行处理完执行比如统计总数是处理日志、统计数据的神器。基本结构awk pattern {action} file # awk 基本语法匹配模式后执行动作示例awk -F : {print $1, $7} /etc/passwd # 以冒号分隔打印第 1 和第 7 列 awk -F : $3 1000 {print $1, $3} /etc/passwd # 打印 UID 大于等于 1000 的用户名和 UID awk {sum $2} END {print sum} sales.txt # 对第二列求和并输出内置变量变量含义NR行号NF字段数FILENAME文件名$0整行$1第一列添加表头表尾awk -F : # 以冒号分隔 BEGIN {print user,shell} # 处理前打印表头 {print $1 , $7} # 每行打印第 1 和第 7 列逗号分隔 END {print done} # 处理后打印 done /etc/passwd # 输入文件五、正则表达式入门grep、awk 这些工具都支持正则表达式来匹配文本记住几个基础元字符就够日常用了^匹配行开头$匹配行结尾.匹配任意一个字符*匹配前面的字符 0 次或多次.*就是匹配任意内容[]匹配括号里的任意一个字符比如[0-9]匹配数字[a-z]匹配小写字母日常最常用的就是配合 grep 过滤日志比如grep ERROR app.log找错误日志。常用元字符符号含义^行首$行尾.任意单字符*前一个字符 0 次或多次[]字符集合[^]排除\转义1 次或多次扩展正则?0 次或 1 次扩展正则{n,m}重复次数范围示例grep -E ^root /etc/passwd # 匹配以 root 开头的行 grep -E bash$ /etc/passwd # 匹配以 bash 结尾的行 grep -E r.t /etc/passwd # 匹配 r 和 t 之间有一个任意字符的行 grep -E ro*t /etc/passwd # 匹配 r 后跟 0 个或多个 o再跟 t grep -E [0-9] /etc/passwd # 匹配包含任意数字的行注意上面几个例子使用的是基本正则表达式可以不加-E。但如果使用了、?、{}、|等扩展元字符则必须使用grep -E或者加上反斜杠进行转义如\、\|、\{\}简单邮箱匹配grep -E ^[A-Za-z0-9._%-][A-Za-z0-9.-]\.[A-Za-z]{2,}$ emails.txt # 匹配邮箱格式简单手机号匹配grep -E ^1[3-9][0-9]{9}$ phones.txt # 匹配 11 位手机号六、综合案例日志统计与服务巡检1. 统计访问日志 Top 10 IPawk {print $1} access.log | sort | uniq -c | sort -nr | head -n 10 # 提取第一列 IP排序去重计数按次数倒序取前 10awk提取把我要找的东西比如 IP抠出来。sort排队按字母顺序排队让同一个 IP 站在一起。uniq -c报数让挨在一起的 IP 报个数记录每个 IP 出现了几次。sort -nr排名按照“报数”的数值大小从高到低排好队。head颁奖取排在最前面的前几名。2. 统计状态码awk {print $9} access.log | sort | uniq -c | sort -nr # 提取第九列状态码排序去重计数按次数倒序3. 服务健康检查#!/usr/bin/env bash # 指定 bash 解释器 check_url() { # 定义检查 URL 的函数 local url$1 # 第一个参数为 URL if curl -fsS --max-time 5 $url /dev/null; then # 静默请求 URL超时 5 秒 echo OK $url # 成功则输出 OK else # 否则 echo FAIL $url # 输出 FAIL fi # 结束 if } # 函数结束 for url in \ # 遍历 URL 列表 http://127.0.0.1:8080/health \ # 第一个健康检查地址 http://127.0.0.1:9090/health # 第二个健康检查地址 do # 循环开始 check_url $url # 调用检查函数 done # 循环结束在实际写脚本做健康检查时我们只关心接口是返回 200 还是 500根本不关心它吐出来的是什么数据。把curl的结果重定向到/dev/null就是给命令装个消音器保持终端整洁。配合-f参数只要接口异常curl就会自动报错退出这样if就能精准捕捉到故障并触发告警了。-f(fail)当 HTTP 状态码是 4xx 或 5xx 时curl会直接报错退出返回非0状态码而不是把错误页面的 HTML 打印出来。这在健康检查中极其关键如果没有-f即使服务器返回了 500 错误curl也会认为请求成功并返回 0。-s(silent)静默模式不显示进度条和错误信息。-S(show-error)配合-s使用表示在静默模式下如果发生错误仍然把错误信息显示出来。--max-time 5设置超时时间为 5 秒。防止目标服务卡死导致curl一直挂在那里不返回拖垮整个巡检脚本。4. 磁盘巡检#!/usr/bin/env bash # 指定 bash 解释器 usage$(df / | awk NR2 {print $5} | tr -d %) # 获取根分区使用率并去掉 % if [ $usage -ge 90 ]; then # 如果使用率大于等于 90 echo 警告根分区使用率 ${usage}% # 输出警告 elif [ $usage -ge 70 ]; then # 否则如果大于等于 70 echo 注意根分区使用率 ${usage}% # 输出注意 else # 否则 echo 正常根分区使用率 ${usage}% # 输出正常 fi # 结束 if查出根目录/的磁盘使用率并去掉百分号%只保留纯数字比如把40%变成40。之所以要去掉%是因为在 Shell 脚本里if只能对纯整数做大小比较比如[ $usage -ge 90 ]。如果带着%Shell 会报错类似于“期待整数却得到了字符串”。我们把这一行拆解开来一步步看它的数据是怎么变化的1.df /查磁盘作用查看根目录/的磁盘使用情况。执行结果它会输出一个带表头的文本表像这样textFilesystem 1K-blocks Used Available Use% Mounted on /dev/sda1 51200000 20000000 31200000 40% /2.| awk NR2 {print $5}抓取第五列管道符|把df /的输出结果像流水线一样递给awk。NR2NR是 awk 的内置变量代表行号Number of Record。NR2意思是只处理第二行因为第一行是表头第二行才是真实数据。$5代表当前行的第五列默认按空格分隔。执行结果它精准地提取出了第二行的第五列即40%。3.| tr -d %删掉百分号tr字符转换或删除工具translate。-d代表删除delete。%指定要删除的字符。执行结果把40%里面的%强行删掉输出纯数字40。4.usage$(...)变量赋值$(...)这是 Shell 的命令替换语法。它的作用是把括号里面那条长长的流水线跑完然后把最终的输出结果比如40赋值给前面的变量usage。执行结果此时变量$usage的值就是40。七、小结Shell 脚本的核心是变量、条件、循环、函数再加上文本处理三件套grep、cut、awk。写脚本时建议用bash -x script.sh调试用shellcheck检查语法和常见坑变量加双引号避免空格问题脚本中尽量使用绝对路径重要操作前先echo确认再执行从 Linux 目录结构、基础命令、系统管理一直到 Shell 脚本自动化把整个大模型开发环境需要的 Linux 基本功全部过了一遍。这些内容不需要死记硬背多在实际环境里敲几次、写两个小脚本比如自动清理旧日志、批量备份文件自然就熟练了这也是我自己学习过程中的一点沉淀。

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

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

免费获取报价 →
↑