Shell编程基础与进程管理
2026/6/27 3:19:03 网站建设 项目流程

写在前面

今天学两块内容——Shell编程基础和进程管理。前者是"怎么让Linux帮我干活",后者是"Linux里面的事都是怎么跑的"。看起来不相关,但其实是一条线:你写Shell脚本让系统干活,系统靠进程来执行这些活。那就从最简单的表达式开始说。


第一部分:Shell编程 — 给Linux定个规矩

1.1 表达式 — Shell的"语法课"

Shell表达式说白了就三件事:算数判断比较

运算表达式 — 小学算数
$((3 + 5)) # 输出8 $((10 % 3)) # 输出1(取余数)

没啥好说的,跟计算器一样。

测试表达式 — 问问题

Shell里问问题,答案不是"对"就是"错":

[ 条件 ] 写成真(0) 写成假(非0)

坑来了:Shell里执行成功返回0,失败返回非0。跟绝大多数编程语言正好相反!C语言里0是假,1是真。Shell里0是真,1是假——别记反了

整数比较 — 谁的数更大
运算符含义记忆法
-eq等于 (equal)首字母e
-ne不等于 (not equal)首字母n
-gt大于 (greater than)g=greater
-ge大于等于g+equal
-lt小于 (less than)l=less
-le小于等于l+equal
[ $age -gt 18 ] && echo "成年了"

1.2 单括号 vs 双括号 — 两个"判官"的区别

Shell里判断条件有两种写法:[ ](老祖宗)和[[ ]](新一代)。跟iPhone一样,新的比旧的功能多:

特性[ ][[ ]]
逻辑与-a&&
逻辑或-o\|\|
正则匹配=~
通配符匹配支持 ✅
可读性一般(字母命名的运算符)更好

结论:能用[[ ]]就用[[ ]],除非你的脚本要在上古 Shell 上跑。

# 判断是不是纯数字 [[ $var =~ ^[0-9]+$ ]] && echo "是纯数字" ​ # 判断文件是不是jpg [[ $file == *.jpg ]] && echo "是图片"

1.3 流程控制 — 让Shell有脑子

if 语句 — 做决策
if [[ $score -ge 60 ]]; then echo "及格了" elif [[ $score -ge 30 ]]; then echo "还有救" else echo "回家种地" fi

注意if后面的; then——分号表示"一句话里换行"。也可以写成:

if [[ $score -ge 60 ]] then echo "及格了" fi
case 语句 — 做选择题

适合那种"值就是这几个之一"的场景:

case $1 in start) echo "启动服务" ;; stop) echo "停止服务" ;; restart) echo "重启服务" ;; *) echo "用法: $0 {start|stop|restart}" ;; esac

caseif-elif-elif-elif清爽太多,看到;;就相当于"这个case结束"。最后的*)是默认分支,类似 switch 里的 default。


第二部分:进程管理 — Linux的"人口管理"

2.1 进程到底是什么?

程序是菜谱,进程是在做菜的过程。

程序(菜谱)放在磁盘上,是一堆静态的指令。当你双击(或执行)它,操作系统把它加载到内存里,分配资源、调度执行——这时候它才叫进程

进程由三部分组成:

┌─────────────────────────────────┐ │ 进程 = 代码 + 数据 + PCB │ └─────────────────────────────────┘

拿做饭来类比:

  • 代码(Text) = 菜谱上的步骤(怎么切菜、什么时候放盐)

  • 数据(Data/Heap/Stack) = 锅里的食材(变量、状态)

  • PCB(Process Control Block) = 你脑袋里记着的"做到哪一步了"

PCB是最关键的部分——它是进程的身份证。操作系统就是靠PCB来管理所有进程的。PCB里记录的东西包括:PID(身份证号)、状态(在排队还是在干活)、优先级(你是VIP还是普通号)、CPU寄存器值(刚才做到哪一行了)。

2.2 进程的五种状态 — 一个人的五种"状态"

在Linux里,一个进程从生到死会经历这些状态:

创建 → 就绪 ↔ 运行 ↔ 阻塞 ↓ 终止

拿医院挂号看病来理解:

🟢创建(New)

你到医院前台,挂号了,护士在电脑里录入你的信息(创建PCB),给你分配病历本(分配资源)。

🟡就绪(Ready)

你拿号坐着等,啥也没干,就等大屏幕叫号(等待CPU分配)。你已经准备好了一切,就差CPU了

🔴运行(Running)

大屏幕叫到你,你进了诊室见医生(获得CPU,正在执行)。单核CPU同一时间只能有一个进程在跑,就像只有一个医生一次只能看一个病人。

🔵阻塞(Blocked/Waiting)

医生说"你先去拍个CT,片子出来再来找我"。你出去等片子(等待I/O完成),这时候医生可以看下一个病人(CPU被调度走了)。注意:你已经拿到CPU了,但因为要等某个事件主动放弃了

终止(Terminated)

看完了,拿药走人,护士注销你的信息(回收资源)。

就绪 vs 阻塞的区别(面试高频题)

  • 就绪:万事俱备,只差CPU(你坐在诊室门口像等号,门一开就能进)

  • 阻塞:CPU来了你也干不了(你去拍CT了,就算医生喊你你也没法看病)

2.3 进程是怎么生出来的?— fork 和 clone

这是这块最重要的知识点,面试必问。

fork() — 影分身之术
pid_t pid = fork(); if (pid == 0) { // 这是子进程 printf("我是儿子,我的PID是%d\n", getpid()); } else if (pid > 0) { // 这是父进程 printf("我是爸爸,我儿子的PID是%d\n", pid); }

fork()会创建一个几乎完全一样的子进程:

  • 子进程复制了父进程的代码段、数据段、堆、栈

  • 父进程收到子进程的PID,子进程收到0

  • 如果fork失败,返回 -1

但如果每次都完全复制,太浪费内存了——父子进程大部分数据是一样的,干嘛复制两份?

这就引出了写时复制(COW, Copy-On-Write):

刚开始父子进程共享同一块物理内存,只是各自的虚拟地址映射到同一块物理内存。只有当其中一方试图修改数据时,内核才真正复制一份给修改方。这就像:你和你爸住一起,客厅的电视共用。只有当你和你爸同时想看不同频道的时候,才会再买一台电视。

clone() — 轻量级分身

fork()是复制所有,而clone()可以选择性共享

  • 共享内存空间 → 线程

  • 不共享内存空间 → 普通进程

  • 共享文件描述符 → 同一组打开文件

本质是:在Linux内核里,线程和进程底层都是用 task_struct 表示的,线程只是"共享了大部分资源的进程"

你现在常用的pthread_create底层就是调用的clone()

2.4 进程管理命令 — Linux的"人口普查工具"

① 看谁在跑 — ps(静态快照)
ps -ef # 标准格式,所有进程 ps aux # 详细格式,含CPU/内存占用 ps aux | grep nginx # 只找nginx相关

-efaux是两种流派,用哪个都行,习惯哪个用哪个。

② 谁生了谁 — pstree(家族树)
pstree # 树状展示父子关系,非常直观 pstree -p # 带上PID

你会发现systemd是所有进程的老祖宗(PID为1),所有进程追根溯源都是它生出来的。

③ 实时监控 — top(动态直播)
top # 跑起来像Windows的任务管理器

进去之后按:

  • P— 按CPU排序

  • M— 按内存排序

  • k— 输入PID杀进程

  • q— 退出

④ 查内存 — free
free -h # 友好格式,显示GB/MB
⑤ 看端口 — lsof(谁占了我的端口?)
lsof -i :80 # 看谁占用了80端口 lsof -Pti :80 # 只输出PID,适合写脚本用

这是排查端口冲突的经典三板斧:lsof -i :端口→ 拿到PID →kill

⑥ 系统整体性能 — vmstat
vmstat 1 5 # 每隔1秒刷新一次,总共5次

输出里重点关注:

  • r(run queue):排队的进程数,持续大于CPU核数说明CPU不够用

  • b(blocked):被阻塞的进程数,过高说明I/O瓶颈

  • si/so:swap换入换出,非0说明内存紧张


2.5 进程控制 — 生杀大权

温柔的杀 vs 暴力的杀
kill 1234 # 发SIGTERM(15),请求进程自己退出 ← 温柔版 kill -9 1234 # 发SIGKILL(9),强制杀死 ← 暴力版

能用kill 1234就别用kill -9。SIGTERM 相当于跟进程说"请收拾一下下班",SIGKILL 相当于直接断电拔网线。很多程序收到 SIGTERM 会做清理工作(保存数据、关闭文件等),收到 SIGKILL 啥都来不及做。

按名字杀
killall nginx # 杀掉所有nginx进程 killall -i nginx # 杀之前问问你,防止误杀 ​ pkill -U test # 杀掉用户test的所有进程 pkill -t pts/1 # 杀掉某个终端的所有进程

killallpkill的区别:前者按进程名匹配,后者更灵活(可以按用户、终端等条件)。

抢CPU — nice/renice

Linux是"多任务"系统,但CPU只有一个(单核)。那谁先谁后?靠优先级

优先级范围:-20(最高优先级) ~ 19(最低优先级) 默认值:0 数字越小,越优先
# 启动时指定优先级 nice -n 10 ./slow_script.sh # 低优先级跑,让出CPU给重要任务 nice -n -5 ./urgent_task.sh # 高优先级跑(需要root权限) ​ # 运行时调整优先级 renice -n 5 -p 1234 # 把PID 1234的优先级改成5 renice -n 10 -u test # 把用户test的所有进程优先级改成10

类比:超市结账

  • nice值-20 ≈ VIP通道,优先结账

  • nice值19 ≈ 你拿了一车东西但只有几件,让后面拿一瓶水的先结

  • 默认0 ≈ 普通排队

2.6 前后台任务 — 一个人当两个人用

想象你在终端前:

  • 前台:你眼前正在运行的程序,占着你的终端

  • 后台:在幕后默默运行的程序,不占用终端操作

# 启动后放到后台 tar -czvf backup.tar.gz /data & ​ # 查看所有后台任务 jobs jobs -l # 带PID ​ # 调回前台 fg %1 # 把1号后台任务调到前台 ​ # 暂停的前台任务在后台继续跑 bg %1 # 恢复暂停的1号后台任务 ​ # 杀后台任务 kill %1 # 通过任务编号杀

典型场景:

  1. tar打了大包,不想干等,按Ctrl+Z暂停

  2. bg %1把它放后台继续跑

  3. 继续敲其他命令

  4. 想查进度fg %1调回前台查看


要点速查卡

Shell编程

[ 条件 ] — 单括号(传统) [[ 条件 ]] — 双括号(推荐,支持正则 && ||) 整数比较:-eq -ne -gt -ge -lt -le 逻辑运算符:&&(与) ||(或) !(非) case语法: case $var in pattern1) cmd1 ;; pattern2) cmd2 ;; *) default ;; esac

进程管理

进程三要素:代码 + 数据 + PCB(最重要的身份证) 五种状态:创建 → 就绪 ↔ 运行 ↔ 阻塞 → 终止 创建方式:fork()(完全复制+COW)| clone()(选择性共享=线程) ​ 常用命令: ps -ef / ps aux # 看进程 pstree -p # 看父子关系 top # 动态监控 lsof -i :端口 # 查端口占用 free -h # 看内存 vmstat 1 5 # 看系统性能 kill -9 PID # 强制杀 nice -n N cmd # 设置优先级启动 jobs / fg / bg # 前后台管理

最后说几句

今天学的东西有个很清晰的脉络:

Shell编程→ 是用代码跟Linux对话的方式表达式和流程控制→ 是语法规则进程→ 是Linux执行这些代码的方式fork/clone→ 是进程如何"生孩子"(面试必考)ps/top/kill→ 是你在运维中每天都要用的"人口管理工具"

把进程的五种状态流转fork()的原理理解透,这两个是面试最爱问的。其他的命令多用几次就记住了,不用硬背。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询