本节摘要:终端是人机接口的载体,Shell 是解释并执行命令的程序,Bash 是 Linux 上事实标准的 Shell。本节从一起"图形界面卡死丢失全部管理入口"的故障讲起,覆盖提示符解读、常用终端操作、SSH 远程连接、管道与重定向的原理,并写出第一个带错误处理的 Shell 脚本。
还是真实事件。某公司的运维平台跑在一台物理服务器上,装了桌面环境,值班同学平时都用机器本地的图形终端操作。某个下午,桌面会话因为显卡驱动的兼容问题整个卡死:鼠标不动、键盘无响应、黑屏但机器还在运行——Web 服务还在正常对外提供服务,因为服务进程根本不依赖桌面。
问题来了:怎么进去处理?机器在机房,跑到机房插键盘显示器是一种办法,但那台机器在异地托管机房,过去要三个小时。最后是机房值守的工程师用救援模式登进去重启了图形服务才解决。事后复盘发现两个漏洞:机器开着 SSH 服务,但没人记得密钥放哪;所有运维操作都依赖图形会话,单点故障。
这个事故的技术教训非常集中:图形界面是建立在大量软件层之上的奢侈品,层越多越容易坏;而 SSH 加命令行是几乎不会坏的底线通道。这一节就把这条底线通道变成你的主战场。
先厘清概念。终端是那个黑窗口本身,负责显示字符、接收按键,现在的终端都是软件模拟的。Shell 是跑在终端里的命令解释器——你敲一行,它解释一行,找到对应程序去执行。Bash 是 Linux 上最主流的那个 Shell,几乎每台发行版都预装它。三者关系一句话:终端是显示器加键盘的化身,Shell 是里面的翻译官,Bash 是这位翻译官的名字。
登录后看到的那行提示符,信息量比看起来大:
deploy@app-01:~/app/logs$
拆开读:deploy 是当前用户;app-01 是主机名;~/app/logs 是当前目录,波浪号代表当前用户的家目录;美元符号表示这是普通用户,如果是井号就是 root——看到井号要本能地紧张一分,因为你拥有的破坏力已经没有边界。
日常终端操作先掌握这几个就够起步:clear 清屏;按一次 Tab 补全命令或路径,按两次列出所有候选;上箭头翻历史命令;Ctrl 加 C 终止当前前台命令。其中 Tab 补全的价值被严重低估——它不只是省事,还能避免拼写错误,补不出来就说明命令没装或路径不对,等于一次免费校验。
SSH 让你从自己的电脑登录到任何网络可达的 Linux 机器,获得一个远程终端。它就是上一节事故里缺的那条底线。
登录的命令形态:
ssh deploy@192.168.1.30
第一次连接会提示确认对方指纹,输入 yes 后输入密码即可登入。但生产环境的标准做法是密钥登录:在本地生成一对密钥,把公钥放到服务器上,之后登录不需要密码,而且可以关闭服务器的密码登录选项,让全网扫描的暴力破解彻底无效。
生成密钥对:
ssh-keygen -t ed25519
Generating public/private ed25519 key pair. Enter file in which to save the key (~/.ssh/id_ed25519): Your identification has been saved in ~/.ssh/id_ed25519 Your public key has been saved in ~/.ssh/id_ed25519.pub
一路回车用默认值即可(生产环境建议给密钥加口令)。然后把公钥传到目标机器:
ssh-copy-id deploy@192.168.1.30
之后 ssh 登录不再询问密码。最后一次用密码登录后,编辑服务器上的 SSH 配置,把密码认证关掉并重启服务:
sudo sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config sudo systemctl restart ssh
⚠️ 常见坑:改 SSH 配置前先开一个新终端验证能登录,再关闭旧终端。直接在唯一会话里改配置重启服务,一旦改错,你就把自己锁在了门外——这是每个运维都听过无数遍、但仍有人亲身经历的事故。
在终端敲下一行命令按下回车,中间发生了什么?理解这条链路,一半的"灵异现象"都会变成因果清晰的过程。
Bash 拿到你输入的这一行,先做展开:波浪号展开成家目录路径,变量名换成变量值,通配符按当前目录的文件展开成具体名字。然后在搜索路径里找命令对应的程序。搜索路径存在一个环境变量里,看一下:
echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Bash 按冒号分隔的顺序在这些目录里找同名程序,找到第一个就用它,找不到就报 command not found。这解释了两个常见困惑:为什么刚装好的程序敲了说找不到——它所在的目录不在列表里;为什么同名命令执行的不是你想要的版本——排在前面的目录里有另一个同名程序。用 which 一查便知:
which python3
/usr/bin/python3
找到程序后,Bash 通过系统调用创建子进程去执行它——回顾 1.1 节的分层图,这里正是用户态内部的一次分工。命令退出时会给 Bash 一个退出码,零表示成功,非零表示失败。这个不起眼的数字是后面脚本错误处理的基石:
ls /etc/passwd echo $?
/etc/passwd 0
换成不存在的路径,退出码就变成 2。记住这个变量,写脚本时全靠它判断上一步成没成功。
1.1 节说过每个进程出生就自带 0、1、2 三个文件描述符:标准输入、标准输出、标准错误。管道和重定向做的事情,就是在进程出生前偷偷改掉这三个描述符的指向。
重定向用尖括号。大于号把标准输出写到文件,两个大于号是追加而不是覆盖:
df -h > disk_report.txt date >> disk_report.txt
一条非常实用但很多人不知道的写法:两个And一个大于号,把标准输出和标准错误一起接住。跑长任务时把全部输出落盘,出问题才有据可查:
./run_backup.sh > backup.log 2>&1
更贴身的例子是这份日志末尾追加重定向的心得。理解之后你会发现,"程序往屏幕打日志"和"程序把日志写进文件"在 Unix 眼里是同一件事——屏幕也只是描述符 1 指向的一个文件而已。
管道是竖线,把左边进程的标准输出接到右边进程的标准输入,两个程序像水管一样串起来。来一个马上能用的例子——统计当前系统里占用内存前十的进程:
ps aux --sort=-%mem | head -10
再来一个经典的:从访问日志里统计出现次数最多的来源地址。
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -5
4237 10.0.3.11 892 10.0.3.14 310 203.0.113.7 55 198.51.100.22 12 192.0.2.88
一行管道做了五件事:取出第一列、排序、去重计数、按次数倒排、取前五。这正是 Unix 哲学的缩影——每个工具只做一件小事,组合起来解决大问题。第 2 章会把这套组合拳展开成完整的一章。

把命令保存成可重复执行的文件,就是脚本。我们从运维最常见的需求出发:磁盘空间巡检,超过阈值就告警。下面这个脚本不到二十行,但包含了工程化脚本的全部骨架要素。
#!/usr/bin/env bash # 磁盘空间巡检:任一挂载点超过阈值即输出告警 set -u THRESHOLD=80 REPORT="/tmp/disk_check_$(date +%Y%m%d).log" df -h --output=pcent,target | tail -n +2 | while read -r pcent target; do used=${pcent%\%} if [ "$used" -ge "$THRESHOLD" ]; then echo "告警:$target 已用 ${used}% 超过阈值 ${THRESHOLD}%" | tee -a "$REPORT" fi done if [ ! -s "$REPORT" ]; then echo "巡检完成:所有挂载点均低于阈值" exit 0 fi exit 1
逐块拆解。第一行井号加感叹号开头的是固定格式,告诉内核用 Bash 来执行这个文件。set 减 u 的作用是引用了未定义变量时立即报错,避免空变量悄悄扩散成大事故。主循环逐行读入每个挂载点的使用率和路径,把百分之号从数字后面剥掉,与阈值比较。tee 命令让告警既上屏幕又落盘。最后的退出码逻辑让这个脚本可以被上层系统调用:零表示健康,非零表示有告警——脚本不说话,退出码就是它的语言。
赋予执行权限并运行:
chmod +x disk_check.sh ./disk_check.sh
告警:/ 已用 84% 超过阈值 80%
这台机器正好根分区超了阈值,脚本立刻抓了出来。把这样的脚本挂到定时任务里,就是最朴素的监控系统雏形。
💡 关键直觉:脚本的价值不在复杂,在于把"半夜要靠人脑记住的操作"变成"白天就能测好的代码"。每个脚本都应该有退出码、有日志、有失败处理——这三样是玩具和工具的分界线。
它们更友好:自动建议、更好的补全、更漂亮的提示符。但学习期我建议留在 Bash,理由有两个。其一,Bash 是所有发行版的出厂默认,你接手的每台服务器上都有它,而 fish 不是 Bash 的严格超集,脚本语法有差异,坏习惯会传染。其二,教程、书籍、同事的脚本全是 Bash 方言,学 Bash 的资料匹配度最高。等你熟悉到能说清"这句为什么这样写",再换 zsh 加插件享受便利不迟——那时你分得清哪些是 Shell 的能力、哪些是插件的魔法。
乱码几乎都是编码不一致造成的。用 locale 命令看当前区域设置,重点确认字符集是不是 UTF-8;再检查你本地终端软件的编码设置与服务器一致。远程连接时两边都说 UTF-8,乱码基本消失。这也是为什么公司规范里通常把统一字符集写进装机清单——编码问题预防的成本是一行配置,治理的成本是翻遍所有历史日志。
先别慌着关窗口。Ctrl 加 C 尝试终止当前命令;如果终端完全没反应,试试 Ctrl 加 Q——有人不小心按到 Ctrl 加 S(老式终端的"暂停输出"流控),终端看起来就像死了一样,Q 是解锁键。这两组按键解决了九成"终端假死"。真正的会话挂死可以用 SSH 的转义序列强制断开:回车后输入波浪号加句点。
用增量搜索代替线性翻找:Ctrl 加 R,输入关键词,Shell 会从历史里模糊匹配最近一条,再按 Ctrl 加 R 继续往前找。另一个技巧是给历史加时间戳记录,排查"当时到底执行了什么"时就是铁证。养成随手 Ctrl 加 R 的习惯,比往上按三十次箭头优雅得多。
第 1 章到这里收束。你已经有了认知模型、装好了系统、掌握了主战场。下一章进入命令的深水区——文件操作、文本过滤、系统信息、查找归档四类高频武器,全部从真实故障现场出发。