拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Shell脚本保姆级教程:从基础语法到自动化运维实战

很多刚开始接触 Linux 的朋友尤其是想转行运维的读者都有类似的困惑看到别人在终端里敲几行命令就能自动完成一堆任务自己却还在一个个手动输入写了几个命令重启服务器后又得重新来一遍。其实这些问题的答案都指向同一个方向——Shell 脚本。这篇文章我会从零开始把 Shell 脚本涉及的背景概念、环境准备、核心语法、完整实战案例和常见排查思路一次讲清楚。内容尽量做到保姆级既有最小示例也有能直接用到工作中的系统巡检脚本。如果你正准备转行运维或者刚入职做服务器维护这篇文章值得收藏起来慢慢看。1. 背景与核心概念1.1 Shell 是什么Shell 脚本又是什么先来解决最基础的问题。Shell 中文可以翻译为“壳”它是用户与 Linux 内核之间的桥梁。你在终端里敲入一条命令Shell 负责解释这条命令然后交给内核去执行最后把结果返回给你。常见 Shell 有bashLinux 默认最多的 Shell功能全面也是本文使用的 Shell。sh最古老的 Shell很多脚本第一行写的都是#!/bin/sh。zshmacOS 默认 Shell交互体验好。csh / tcsh / ksh其他常见 Shell日常使用较少。Shell 脚本简单理解就是把多条 Linux 命令按照一定的逻辑写进一个文件里然后交给 Shell 去逐行解释执行。脚本可以包含变量、条件判断、循环、函数、管道、重定向等编程语言特性。用一句话概括Shell 是解释命令的工具Shell 脚本是用这种工具语言编写的程序。1.2 Shell 脚本解决了什么问题运维工作中大量任务都是重复性的比如每天检查服务器 CPU、内存、磁盘使用率。定期备份数据库和关键配置文件。批量创建用户、批量分发公钥。定时清理过期日志。监控某个进程是否存活宕掉后自动拉起。这些任务如果全部手动执行效率低且容易遗漏。而写成 Shell 脚本后配合 crontab 定时任务就能实现自动化运维这也是为什么 Shell 脚本是运维工程师的必备技能。1.3 Shell 脚本与 Linux 命令的区别新手容易混淆“命令”和“脚本”这两个概念。区别项Linux 命令Shell 脚本存放位置系统自带或软件安装产生用户自己创建的文件执行方式输入即执行需要赋予执行权限或使用 bash 执行逻辑处理单条命令功能支持 if/for/while 等编程逻辑复用性命令执行完即结束可以反复执行传给不同参数简单说命令是脚本的零件脚本是命令的组合。学习 Shell 脚本的前提是你已经掌握了一些常用命令例如grep、awk、sed、df、free、ps等。如果这些命令还不太熟建议先反复练习 Linux 基础命令再回来学习脚本。2. 环境准备与版本说明2.1 操作系统要求Shell 脚本本身不依赖复杂的运行环境只要你的机器是 Linux 或者安装了终端模拟器的 macOS基本都可以直接学习。本文所有示例基于以下环境操作系统CentOS 7 / Ubuntu 20.04均可Shell 版本bash 4.x编辑器vim / vscode 均可如果你使用的是 Windows建议安装虚拟机或者使用 WSLWindows Subsystem for Linux不要在 Windows 命令提示符下直接运行 Shell 脚本因为语法体系完全不同。2.2 查看当前 Shell 类型登录 Linux 后先来确认当前使用的 Shellecho $SHELL输出示例/bin/bash看到/bin/bash说明当前 Shell 是 bash。也可以查看系统支持哪些 Shellcat /etc/shells输出示例/bin/sh /bin/bash /usr/bin/bash /bin/rbash /usr/bin/rbash2.3 创建一个测试目录为了方便后续练习我们创建一个专门的工作目录mkdir -p ~/shell_lab cd ~/shell_lab后续所有脚本都放在这个目录中。如果你用的是 root 用户路径就是/root/shell_lab。2.4 编辑器选择写脚本没必要用太复杂的 IDEvim足够了。如果你对 vim 不熟也可以使用yum install -y vim # CentOS apt install -y vim # Ubuntu或者直接用 vscode 远程连接服务器编辑方式不限关键是能保存为纯文本文件即可。3. 核心语法与配置拆解这一部分我们拆解 Shell 脚本最常用的几个知识点变量、条件判断、循环、函数、重定向与管道。每一块都会给出最小示例并解释关键点。3.1 变量脚本的存储单元变量是 Shell 脚本中最基础的组成部分。它的作用是把值保存下来后续可以直接引用。#!/bin/bash # 变量定义等号两侧不能有空格 namecsdn version1.0 echo 欢迎阅读 ${name} 的 Shell 教程 echo 当前脚本版本: $version输出欢迎阅读 csdn 的 Shell 教程 当前脚本版本: 1.0关键点变量名和等号之间不能有空格。引用变量时可以使用$变量名或${变量名}。推荐使用${变量名}尤其在变量后面紧跟其他字符时花括号可以明确变量边界。字符串建议加双引号避免空格导致意外拆分。除了用户自定义变量Shell 还有位置变量和预定义变量#!/bin/bash echo 脚本名称: $0 echo 第一个参数: $1 echo 第二个参数: $2 echo 参数个数: $# echo 所有参数: $执行bash test.sh hello world输出脚本名称: test.sh 第一个参数: hello 第二个参数: world 参数个数: 2 所有参数: hello world这类变量在写批量处理脚本时非常有用可以根据用户传入的参数动态执行任务。3.2 条件判断让脚本拥有决策能力Shell 脚本之所以能完成复杂任务离不开判断语法。最常用的就是if和case。if 语法示例#!/bin/bash score85 if [ $score -ge 90 ]; then echo 优秀 elif [ $score -ge 60 ]; then echo 及格 else echo 不及格 fi这里需要特别注意[和]两侧必须留空格。条件表达式中的-ge表示大于等于常用比较符还有-eq等于、-ne不等于、-gt大于、-lt小于、-le小于等于。if必须以fi结尾。文件判断是运维场景中最高频的示例#!/bin/bash if [ -f /etc/passwd ]; then echo /etc/passwd 文件存在 else echo 文件不存在 fi常用文件判断参数参数含义-f是否为普通文件-d是否为目录-e文件或目录是否存在-r-w-s文件是否存在且不为空case 语法示例#!/bin/bash case $1 in start) echo 服务启动中... ;; stop) echo 服务停止中... ;; restart) echo 服务重启中... ;; *) echo 用法: $0 {start|stop|restart} ;; esac这种写法常用于服务管理脚本比if的嵌套更清晰。3.3 循环重复任务的克星运维中经常需要对一批文件、一批服务器、一批用户做相同操作这时候循环就派上用场了。for 循环示例#!/bin/bash # 遍历列表 for ip in 192.168.1.10 192.168.1.11 192.168.1.12; do echo 正在检查服务器: $ip done#!/bin/bash # 遍历数字范围 for i in {1..5}; do echo 第 $i 次执行 done#!/bin/bash # C 语言风格的 for 循环 for ((i1; i10; i)); do echo 当前数值: $i donewhile 循环示例#!/bin/bash count1 while [ $count -le 5 ]; do echo count $count count$((count 1)) done需要特别提醒的是$((count 1))是 Shell 算术运算语法不能写成count count 1否则会报错。3.4 函数复用代码块一个稍大规模的脚本如果全部指令堆在一起后期维护会非常痛苦。把重复使用的逻辑封装成函数是推荐的工程习惯。#!/bin/bash # 定义函数 check_disk() { echo 磁盘使用情况 df -h } check_memory() { echo 内存使用情况 free -m } # 调用函数 check_disk check_memory函数也可以接收参数#!/bin/bash log_info() { echo [INFO] $(date %Y-%m-%d %H:%M:%S) $1 } log_info 系统巡检开始 log_info 磁盘检查完成注意Shell 函数的参数在函数体内部使用$1、$2引用调用时直接把参数跟在函数名后面即可。3.5 重定向与管道数据处理的重要工具重定向和管道不是语法但它们是 Shell 的灵魂。重定向常用符号将标准输出写入文件覆盖将标准输出追加到文件2将错误输出写入文件将标准输出和错误输出都写入文件从文件读取输入# 标准输出写入文件 echo hello shell output.txt # 标准错误输出单独保存 ls /nonexistent 2 error.log # 正确日志和错误日志分开保存 find /etc -name *.conf conf_list.txt 2 find_error.log管道示例# 查看当前系统进程数 ps -ef | wc -l # 查看 Nginx 进程 ps -ef | grep nginx | grep -v grep # 查看磁盘使用率最高的分区 df -h | awk {print $5, $6} | sort -n | tail -3管道符|的作用是把左侧命令的输出作为右侧命令的输入多个命令可以串联使用。这也是 Shell 最强大、最美妙的地方。4. 完整实战案例编写一个系统巡检脚本前面讲了这么多基础语法接下来我们把这些内容拼装成一个完整的实战案例。这个案例直接面向运维场景一键收集 Linux 服务器关键运行状态并输出报告。4.1 需求分析我们希望实现以下功能查看系统主机名、内核版本、运行时间。查看 CPU 负载情况。查看内存使用情况。查看磁盘使用情况。检测指定服务如 nginx是否在运行。将结果输出到文件并附加时间戳。4.2 创建项目结构mkdir -p ~/shell_lab/system_check cd ~/shell_lab/system_check touch system_check.sh chmod x system_check.shchmod x的作用是给脚本添加可执行权限这样可以直接./system_check.sh运行否则只能使用bash system_check.sh。4.3 编写完整脚本文件路径~/shell_lab/system_check/system_check.sh#!/bin/bash # 描述: 一键系统巡检脚本 # 用法: ./system_check.sh [service_name] # 作者: csdn # 设置输出文件名包含当前时间戳 REPORT_FILEsystem_report_$(date %Y%m%d_%H%M%S).txt # 函数: 输出分隔线 print_line() { echo $REPORT_FILE } # 函数: 写入信息头 write_header() { echo $REPORT_FILE print_line echo $1 $REPORT_FILE print_line } # 开始生成报告 echo 系统巡检报告生成中... # 写入报告标题 echo Linux 系统巡检报告 $REPORT_FILE echo 生成时间: $(date %Y-%m-%d %H:%M:%S) $REPORT_FILE # 1. 系统基本信息 write_header 系统基本信息 echo 主机名: $(hostname) $REPORT_FILE echo 内核版本: $(uname -r) $REPORT_FILE echo 操作系统: $(cat /etc/os-release | grep PRETTY_NAME | cut -d -f2 | tr -d \) $REPORT_FILE echo 运行时间: $(uptime -p) $REPORT_FILE # 2. CPU 负载 write_header CPU 负载情况 uptime $REPORT_FILE echo $REPORT_FILE echo CPU 核心数: $(nproc) $REPORT_FILE # 3. 内存使用情况 write_header 内存使用情况 (单位: MB) free -m $REPORT_FILE # 4. 磁盘使用情况 write_header 磁盘使用情况 df -h $REPORT_FILE # 5. 指定服务状态检测 SERVICE_NAME${1:-nginx} write_header 服务状态检测: $SERVICE_NAME if pgrep -x $SERVICE_NAME /dev/null; then echo 服务 $SERVICE_NAME 正在运行中 $REPORT_FILE else echo 服务 $SERVICE_NAME 未运行 $REPORT_FILE fi # 6. 内存占用 Top 5 进程 write_header 内存占用 Top 5 进程 ps aux --sort-%mem | head -6 $REPORT_FILE # 完成提示 echo echo 系统巡检完成报告已保存到: $REPORT_FILE4.4 运行与验证./system_check.sh nginx如果脚本没有可执行权限会报错Permission denied。此时可以chmod x system_check.sh ./system_check.sh nginx或者直接用 bash 解释器运行bash system_check.sh nginx运行结束后当前目录下会多出一个类似system_report_20250101_120000.txt的文件使用cat查看内容cat system_report_20250101_120000.txt预期输出结构如下Linux 系统巡检报告 生成时间: 2025-01-01 12:00:00 系统基本信息 主机名: localhost 内核版本: 3.10.0-1160.el7.x86_64 操作系统: CentOS Linux 7 (Core) 运行时间: up 3 days, 2 hours, 15 minutes CPU 负载情况 12:00:01 up 3 days, 2:15, 2 users, load average: 0.01, 0.05, 0.03 CPU 核心数: 2 内存使用情况 (单位: MB) total used free shared buff/cache available Mem: 1837 617 446 8 773 1039 Swap: 0 0 0 磁盘使用情况 Filesystem Size Used Avail Use% Mounted on /dev/vda1 40G 12G 27G 31% / 服务状态检测: nginx 服务 nginx 未运行 内存占用 Top 5 进程 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.2 42988 3612 ? Ss Jan01 0:07 /usr/lib/systemd/systemd ...4.5 结合 crontab 实现定时巡检上面的脚本已经可以手动运行。实际生产环境中我们往往需要每天自动执行然后把报告保存下来方便日后查询。用 crontab 实现定时执行crontab -e在打开的编辑器中添加下面一行每天凌晨 2 点执行巡检0 2 * * * /root/shell_lab/system_check/system_check.sh nginx /root/shell_lab/system_check/cron.log 21保存后使用crontab -l可以确认定时任务已生效crontab -l补充说明21的作用是把标准错误输出也重定向到日志文件中避免脚本报错时没有任何记录。5. 常见问题与排查思路Shell 脚本报错不直观对新手不太友好。下面列几个出现频率极高的问题以及对应的排查思路。问题现象常见原因解决思路./test.sh: Permission denied脚本文件没有执行权限chmod x test.sh或改用bash test.shbad interpreter: /bin/bash^M: no such file or directory脚本是在 Windows 上编辑的文件包含 CRLF 换行符使用sed -i s/\r$// test.sh转换格式[ : command not found[与]两侧缺少空格或 if 语句中少了空格检查if [ $var -eq 1 ]; then的写法syntax error near unexpected token fiif 语句没有正确闭合或者关键字写错检查是否缺少fi、done等结束关键字变量值一直为空变量名拼写不一致或等号两侧有空格Echo 输出变量检查确认变量名与定义完全一致5.1 脚本文件编码问题如果你在 Windows 上编辑脚本然后上传到 Linux 执行经常遇到换行符不兼容的问题。可以在 Linux 上使用file命令检查file test.sh如果输出中有CRLF说明需要转换sed -i s/\r$// test.sh转换后再次执行问题通常就会消失。5.2 排查脚本执行过程如果脚本执行结果不符合预期推荐使用bash -x来调试。它可以逐行显示命令执行过程帮助定位出错位置bash -x system_check.sh输出中带有前缀的每一行就是脚本实际执行的命令。通过观察变量当时的值基本能定位到问题所在。5.3 变量名注意大小写Linux 环境变量区分大小写$Name、$NAME、$name是三个完全不同的变量。排查问题时先确认拼写。6. 最佳实践与工程建议学完语法、写完脚本下一步要考虑的就是怎么写得更规范、更可靠。以下是 Shell 脚本工程化的一些建议也是面试和实际工作中经常考察的点。6.1 脚本头部声明每个脚本第一行都应该指定解释器#!/bin/bash虽然有时候不写也能运行但加上它是一个好习惯。系统会明确使用 bash 解释执行避免因默认 Shell 不同导致兼容问题。6.2 变量引用加花括号统一使用${变量名}而不是$变量名例如echo 报告存放于 ${REPORT_DIR}/backup_${DATE}.tar.gz如果不加花括号碰上$REPORT_DIR_2025这样的写法Shell 会尝试寻找名为REPORT_DIR_2025的变量导致取值为空。6.3 字符串变量加双引号引用变量时建议加上双引号尤其是文件路径和参数中可能包含空格或特殊字符时# 推荐 echo 欢迎, ${username} # 不推荐 echo 欢迎, $username反例在变量内容为空时可能会看到奇怪的报错加上双引号能减少这类问题。6.4 脚本开头添加检查和退出机制比较健壮的脚本会在关键命令执行失败后主动退出避免后续命令带着错误状态继续执行#!/bin/bash set -e echo 步骤1: 备份数据库 mysqldump -u root test_db /backup/test_db.sql echo 步骤2: 删除过期备份 find /backup -name *.sql -mtime 7 -deleteset -e的含义是只要脚本中任何一条命令返回非零状态码立即退出脚本。注意不要在不允许退出的临时场景使用否则可能跳过预期逻辑。6.5 日志记录生产环境中的脚本一定要有日志。日志至少应该包含时间、等级、操作内容#!/bin/bash LOG_FILE/var/log/my_script.log log_info() { echo [INFO] $(date %Y-%m-%d %H:%M:%S) $1 $LOG_FILE } log_error() { echo [ERROR] $(date %Y-%m-%d %H:%M:%S) $1 $LOG_FILE }出问题时运维人员第一时间翻日志而不是重新跑一遍脚本看现象。6.6 敏感信息不要硬编码脚本中涉及数据库密码、API Token 等敏感信息时不要直接写成明文。建议使用环境变量#!/bin/bash DB_PASSWORD${DB_PASSWORD:-default_password} mysql -u root -p${DB_PASSWORD} -e SHOW DATABASES;或者将敏感信息放入单独的配置文件中并设置严格的文件权限。不要把生产密码提交到代码仓库这是运维事故的高发源头。6.7 静态检查工具 ShellCheck写了几百行脚本之后肉眼检查很容易遗漏问题。推荐使用 ShellCheck 工具做静态检查。安装# CentOS yum install -y shellcheck # Ubuntu apt install -y shellcheck使用shellcheck system_check.shShellCheck 会输出代码中潜在的语法问题、变量引用问题以及明显不安全的写法非常适合排查隐藏 bug。6.8 注意脚本兼容性如果你的脚本可能在多个 Linux 发行版上运行尽量避免使用某个发行版专属的命令选项。比如CentOS 上使用yumUbuntu 上使用apt。GNU 版本grep、sed与 BSD 版本存在细微差异。可以在脚本中先检测系统类型再执行对应命令#!/bin/bash if [ -f /etc/redhat-release ]; then echo CentOS / RHEL 系统 yum install -y vim elif [ -f /etc/lsb-release ]; then echo Ubuntu / Debian 系统 apt install -y vim else echo 未知系统请手动安装 fi7. 总结怎么练才能真正掌握 Shell 脚本Shell 脚本这门技能最大的特点是上手快进阶慢。语法本身不复杂变量、循环、判断、函数一天就可以学会。但真正拉开差距的是遇到实际问题时能否快速用脚本解决以及写出来的脚本是否健壮、可维护、可复用。如果你现在刚入门建议按下面的路径练习先把 Linux 最常用的命令练熟比如ls、cd、grep、awk、sed、find、df、free、ps、netstat。手动完成 3 到 5 个简单脚本包括变量处理、条件判断、循环输出。把日常工作里重复的操作抽出来写成脚本哪怕最开始脚本只有 10 行。学会使用bash -x调试脚本遇到报错不要慌逐步定位。阅读系统自带脚本比如/etc/init.d/下的服务管理脚本学习别人的写法。Shell 脚本真正的价值不是炫技而是把运维工作中重复、琐碎、易出错的操作变成稳定可靠的一键执行。坚持写一段时间你会发现自己的效率提升非常明显。如果这篇文章对你有帮助可以收藏备用。下一步你可以继续学习定时任务 crontab 的进阶用法。awk、sed、grep 三剑客的深入实战。Shell 脚本调用云厂商 API 实现自动化。Ansible、Python 脚本与 Shell 脚本的分工与配合。动手写今天第一个脚本吧十分钟之后你就已经是写过 Shell 脚本的人了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门