1. 安装的本质不是“装”:文件布局、依赖关系与权限模型
很多人第一次接触Linux,拿到的第一台云服务器,第一件事就是装环境、装软件。按着网上的教程敲apt install nginx,界面刷刷一顿输出,然后浏览器一开,欢迎页出来了,就觉得自己“会”了。直到某天你装了一个新程序,它把旧程序的库文件顶掉了,另一个程序紧接着崩了,你才发现自己压根没搞懂Linux的安装是怎么回事。
在Windows上装软件,本质上是“双击一个巨大的自解压包,它把几千个小文件撒到Program Files、System32、注册表里,然后把快捷方式丢到桌面”。在Linux上,安装的本质完全不同——它是一套把文件按照约定放进系统目录、同时把依赖关系登记清楚、并赋予程序可执行权限的体系。你理解了这一层,再去看任何安装方式,都会有种“原来如此”的通透感。
1.1 Linux程序在系统里留下的“痕迹”
一个Linux程序,安装完成后通常会把这几类东西放进系统:
| 文件类型 | 典型目录 | 说明 |
|---|---|---|
| 可执行文件 | /usr/bin、/usr/local/bin | 你在Shell里敲命令时,系统按PATH变量去这些目录里找程序 |
| 库文件 | /usr/lib、/usr/lib/x86_64-linux-gnu | 程序运行依赖的.so动态链接库,类似Windows的DLL |
| 配置文件 | /etc | 全局配置通常放这里,比如nginx的/etc/nginx/nginx.conf |
| 数据文件 | /var/lib、/opt | 程序运行产生的持久化数据,比如MySQL的数据库文件 |
| 日志 | /var/log | 程序运行记录 |
| 服务脚本 | /etc/systemd/system、/lib/systemd/system | 注册为系统服务,支持开机自启、systemctl start/stop |
也就是说,“安装一个程序”翻译成Linux的动作,就是“把这几个文件分别放到对应的目录,然后让系统知道它叫什么、怎么启动、依赖谁”。其余的,全是细节。
我第一次意识到这件事,是自己写了一个小工具,想装到系统里用。当时直接把编译好的二进制文件拖到了/usr/bin,发现敲命令报“command not found”——折腾半天才发现,光有文件还不够,还得有可执行权限(chmod +x),而且PATH里得包含这个目录。Windows下双击就能跑的程序,在Linux下被拆成了“文件在不在”“能不能执行”“能不能被找到”三个独立的问题。
1.2 动态链接:安装最隐蔽的“隐形依赖”
新手最容易踩的坑,不是文件找不到,而是“程序装了,一运行就报错”。常见的错长这样:
error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory这行报错翻译过来是:程序本体装好了,但它运行时需要一个叫libssl.so.1.1的动态链接库,系统里没有。
动态链接是Linux程序运行的基本方式——程序在编译时并不把所有的库代码打包进自己体内,而是运行时才去系统指定的路径找.so文件来加载。好处是节省磁盘、多个程序共享同一份库;坏处是,程序A依赖库的1.1版本,程序B依赖1.0版本,你升级了库,A可能就崩了。这就是所谓“依赖地狱”的雏形。
理解了这个机制,你就能明白为什么下面要讲的软件包管理器这么重要——它不只是帮你把主程序装上,还会自动把程序依赖的库、数据文件、配置模板一并装好,并且记录依赖关系,升级时告诉你“升级这个包会连带影响哪些其他包”。手动装程序时这些全靠你自己盯着,盯漏一个,后面全崩。
2. 包管理器才是日常主力:apt/dnf背后的依赖解析逻辑
如果你只在Linux上装过三四个软件,可能感受不到包管理器的价值。但当你需要装一个带几十个依赖的桌面环境,或者部署一套LNMP组合时,你会真心感谢那个帮你把依赖全部理顺的工具。
Linux的包管理器分两大生态:Debian系(Ubuntu、Debian、Deepin、麒麟)用apt+dpkg,Red Hat 系(CentOS、RHEL、Rocky、Fedora)用dnf/yum+rpm。命令不一样,但思路高度一致:包管理器维护了一个本地数据库,记录系统里装了哪些包、每个包的版本、彼此之间的依赖关系;安装时自动解析依赖,从仓库里按需拉取并安装额外的依赖包。
2.1 apt安装的完整链路:不只是“下载解压”那么简单
你现在敲一条sudo apt install nginx,背后发生的其实是这么一串事:
apt读取/etc/apt/sources.list和/etc/apt/sources.list.d/下的软件源配置,这些配置告诉它“去哪找软件包”。- 它会先更新本地包的索引(如果本地缓存的索引过期了),这个索引相当于仓库里所有软件包信息的“目录册”。
- 解析
nginx的依赖关系,算出需要哪些额外的包,比如libgd、libssl之类的,形成一个待安装清单。 - 下载所有包到本地缓存目录
/var/cache/apt/archives/。 - 用
dpkg逐个解包、安装、执行安装后脚本。 - 更新本地数据库,让系统“知道”nginx这个包已经装了、装的是哪个版本。
偏Debian系底层的dpkg可以类比为“解包安装器”,它只知道把.deb文件解开、文件归位、记录数据库;apt则是在这之上做依赖解析和仓库管理的“调度员”。你把这两者分清了,今后遇到问题了才能判断是哪一环出的错——依赖没解析好,锅多半在apt;包本身安装脚本报错,问题出在dpkg。
Red Hat 系的dnf和rpm的关系也一样。rpm -ivh xxx.rpm是手动安装一个包,它会傻乎乎地装,缺依赖就直接报错退出;dnf install xxx则会自动把依赖也装齐。所以日常使用永远优先用dnf/apt,手动rpm/dpkg只在特殊场景(比如下载了离线安装包)才碰。
2.2 高频包管理操作:安装、卸载、锁定版本、查依赖
我把日常用得最频繁的一批命令整理在这里,方便直接抄:
| 目的 | Debian/Ubuntu系 | Red Hat系 |
|---|---|---|
| 更新仓库索引 | sudo apt update | sudo dnf makecache |
| 升级所有可升级的包 | sudo apt upgrade | sudo dnf upgrade |
| 安装指定包 | sudo apt install 包名 | sudo dnf install 包名 |
| 卸载但保留配置 | sudo apt remove 包名 | sudo dnf remove 包名 |
| 卸载并清掉配置文件 | sudo apt purge 包名 | sudo dnf remove 包名 |
| 搜索软件包 | apt search 关键词 | dnf search 关键词 |
| 查看某个包的信息 | apt show 包名 | dnf info 包名 |
| 查看某文件属于哪个包 | dpkg -S /路径/文件 | rpm -qf /路径/文件 |
| 查看已安装包的依赖 | apt depends 包名 | dnf repoquery --requires 包名 |
| 列出已安装的包 | dpkg -l | rpm -qa |
这些命令里,我特别想提醒两个:
一是apt remove和apt purge的差别。remove只删程序本体,配置文件留在/etc里;purge连配置文件一起删。如果你卸载一个软件后打算重装一个干净版本,必须用purge,否则旧的配置会残留下来,新装的程序照样读旧配置,你以为是新版的行为,其实是旧配置在作祟。这个坑我踩过不止一次,最典型的就是MySQL重装后密码死活不对,最后发现是残留的配置文件覆盖了新装的默认配置。
二是apt upgrade和apt dist-upgrade的区别。老教程里常看到dist-upgrade,它会在升级包的同时处理依赖变化,甚至可能因此删除一些旧包;普通的upgrade只升级,不额外解决需要变更依赖关系的包。现在的主流Debian/Ubuntu版本里,apt upgrade已经足够应对日常升级,dist-upgrade主要用在跨大版本升级(比如Ubuntu 22.04升到24.04)时,普通用户别乱敲。
2.3 软件源配置:换源之前先搞懂你在换什么
“Linux装软件慢”——这大概是被吐槽最多的问题。很多教程直接让你把/etc/apt/sources.list里的地址换成国内镜像源,然后apt update一下就快了。但我要多说一句:换源的本质,是让包管理器从一个离你更近、速度更快的镜像仓库下载文件,而不是从官方源绕半个地球去取。
改源的时候有两点容易被忽视:
第一点是版本代号要对。Debian系软件源配置里通常带版本代号,比如Ubuntu 24.04的代号是noble。如果你照抄了网上别人22.04的源配置,apt update时大概率会报错或者拉到一堆不兼容的包版本。正确的做法是:先用lsb_release -a看自己的系统代号,再去对应的镜像站查这个代号支持的源列表。
第二点是安全源和更新源都要配置。Ubuntu的源文件里除了main、universe这些软件包组件,还包含security和updates。把它们全注释掉,确实能加速更新,但代价是系统安全补丁也断了。我的建议是只替换国内主干源,security相关的更新保留官方地址,或者用镜像站提供的完整配置块,别为了快把安全更新牺牲了。
3. 源码编译:configure/make/install全链路动手实录
如果说包管理器是“别人帮你把菜做好端上来”,那源码编译就是“买回来食材,自己洗菜切菜下锅”。前者快、省事、标准,但你没得选——包管理器给什么版本你就用什么版本。后者慢、繁琐、容易出问题,但你能干很多前者干不了的事。
哪些场景需要源码编译?我碰到过的大概就这几种:
- 官方仓库里的版本太旧,你需要新特性,而第三方源又不提供新版本。
- 你需要对默认编译参数做裁减,比如去掉某个不需要的模块,或者加上某个默认没开的特性。
- 你用的CPU架构比较特殊,仓库里没有预编译好的包。
- 你想装私有软件,人家只发布源码。
3.1 configure阶段:它在干什么?为什么总报缺东西
以经典的nginx为例,源码编译三连是这个:
./configure --prefix=/usr/local/nginx --with-http_ssl_module make sudo make install./configure这一步劝退了无数新手。很多人看到“configure: error: the HTTP rewrite module requires the PCRE library”就崩溃了,其实这个报错非常好理解——你在告诉nginx编译时我要带rewrite模块,而这个模块依赖PCRE库,configure检查发现系统里没有PCRE的开发文件,于是拒绝继续。
看到这类报错,思路就一条:缺哪个库,就去装哪个库的“开发版”。比如报错说缺PCRE,Debian系就装libpcre3-dev,Red Hat系就装pcre-devel。为什么必须是“开发版”?因为编译时需要的是头文件(.h文件)和对应的.so链接文件,而不仅仅是运行时的库。这个知识点很关键——很多新手看到报错缺库,去apt install pcre,装完再 configure 还是报同一个错,就是因为装的是运行时库,开发头文件没装上。
如果拿捏不准到底要装什么包,一个实用技巧是:把报错里的大写库名截出来去包管理器里搜,比如apt search pcre,看列表里带-dev后缀的那个,装它。Debian系和Red Hat系的开发包命名规律很统一——多数就是“库名+-dev/-devel”。
configure还有一个用途是定制安装路径。--prefix=/usr/local/nginx表示装到/usr/local/nginx,不填的话默认装在/usr/local,好处是你的软件和系统包管理器的文件互不冲突。我在生产环境里编译装软件,几乎都会指定--prefix,这样卸载的时候直接把整个目录删掉,基本不会误伤其他程序。
3.2 make 和 make install:编译到底在忙什么
configure通过之后,目录里会生成一份Makefile—— 这就是编译的“施工图纸”。它记录了:哪些源文件要一起编译、用哪些编译参数、最终生成什么二进制、安装时文件分别复制到哪里。
接下来执行make,它干的活是把源码变成可运行的二进制。这个过程可能很快,也可能旷日持久(我编译过一次Chromium,整整跑了三小时)。如果中途报错,通常是你缺了某个库的头文件,或者编译器版本太老不支持新的语法。前者用上一步的方法补齐依赖,再重新make;后者多半得查一下软件要求的编译器版本。
make成功之后,生成的可执行文件还躺在源码目录里,你可以在当前目录用./nginx这种方式临时跑。真正把文件安装到系统目录,是sudo make install这一步。它严格按照Makefile里的规则,把编译好的二进制复制到--prefix指定目录,把配置文件放到对应位置,可能还会创建日志目录和数据目录。
我一直建议新手把编译安装的三步拆开执行,而不是教程里常见的./configure && make && make install一把梭。为什么?因为如果 install 阶段因为权限问题报错了,前面已编译好的二进制文件其实已经生成了,你只需要sudo make install重新装一遍就行,完全不用从头编译。一条命令连起来跑,每次报错都得从头定位,没必要。
3.3 编译装完程序却敲不了命令:PATH和动态库的坑
源码编译安装最容易出的一个“装完但用不了”的坑——指令找不到。比如我上面指定--prefix=/usr/local/nginx,nginx装好后你在任何目录敲nginx,系统都提示command not found。
原因在于Shell是依靠环境变量PATH里的目录列表去找命令的。nginx装在了/usr/local/nginx/sbin/nginx,而PATH里默认只包含/usr/local/bin、/usr/bin、/bin这类目录,自然找不到它。
解决方式有三种:一是用完整路径执行/usr/local/nginx/sbin/nginx;二是把它的目录加入PATH(改/etc/profile或~/.bashrc);三是在/usr/bin下做一个软链接:ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx。我个人推荐第三种,简单直接,而且对系统影响最小。
比PATH更隐蔽的是动态库路径问题。有些软件装到自定义lib目录后,运行时报错找不到.so文件,比如:
./app: error while loading shared libraries: libfoo.so.1: cannot open shared object file解决办法是让系统知道去哪找这个库。把自定义目录写进/etc/ld.so.conf.d/下新建的一个.conf文件里,然后执行sudo ldconfig刷新动态链接器缓存,就好。如果你只是给当前用户临时测试,也可以设置export LD_LIBRARY_PATH=/path/to/lib:$LD_LIBRARY_PATH,但注意这只对当前Shell生效,重启就没了。
4. 三种高频业务软件安装实战:MySQL、Docker、Python多版本
理论讲了一堆,总得落到具体软件上才有体感。我挑三个最有代表性的场景,说说各自安装时真正的坑在哪。
4.1 MySQL:apt直装未必是最优选
很多人装MySQL上来就sudo apt install mysql-server,装完还很顺手。但如果你的业务要跑很多年,我建议慎重用这种默认源版本。原因有两个:
第一是版本落后。Ubuntu默认源里的MySQL版本往往不是最新甚至不是长期支持版,后面出问题查资料时,版本对不上会非常痛苦。
第二是默认配置和你要的往往不一样。比如默认的datadir在系统盘,你数据盘在/data,后面迁数据库又得折腾。
生产环境我更推荐用MySQL官方提供的APT仓库。步骤大概是:去官网下载对应的仓库配置文件(.deb包),安装它,它会往/etc/apt/sources.list.d/写入官方源,然后:
sudo apt update sudo apt install mysql-server这样得到的MySQL版本新、更新及时,而且官方仓库里自带一堆mysql-server-8.0这类指定版本包,选择余地大。
装完后有个高频坑:MySQL 8.0 默认的认证插件是caching_sha2_password,有些老客户端(比如PHP 7.2之前的mysqlnd)不认这个,连不上库。解决方式是在MySQL里给对应账号指定mysql_native_password:
ALTER USER 'appuser'@'%' IDENTIFIED WITH mysql_native_password BY 'your_password';这个坑,90%的“装好了但程序连不上”都是它引起的。
4.2 Docker:安装脚本里的隐藏知识点
Docker的安装算是比较省心的,官方提供了安装脚本:
curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh但我并不建议你直接跑这个脚本就跑路,原因在于:脚本默认安装的是Docker CE的stable版本,但它不会帮你做用户组授权、也不会帮你配置镜像加速。
装完之后最重要的一件事,是把当前用户加进docker组,否则每次执行docker命令都要加sudo,烦得你想摔键盘:
sudo usermod -aG docker $USER改完用户组需要重新登录(或执行newgrp docker)才生效。
另外,如果你在国内服务器上装了Docker,不做镜像加速的话,拉个镜像能等到天荒地老,还时不时超时中断。配置方式是在/etc/docker/daemon.json里写镜像源地址:
{ "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"] }然后执行sudo systemctl restart docker重启服务。这里要注意daemon.json用了JSON格式,逗号、引号缺一不可,写错了Docker服务直接起不来。改完可以用sudo dockerd --validate-config检查语法(新版本才支持),或者daemon.json改完后马上systemctl start docker看是否报错。
4.3 Python多版本:为什么直接改系统Python是个坏主意
“用Linux装Python”,如果你指的是想装最新的Python,我强烈建议你不要去动系统自带的那个Python。
Ubuntu/Debian系统里的python3是系统底层的“基础设施”,很多系统工具(比如apt的某些插件、gnome-terminal)都依赖它。你要是手贱把它升级到新版本,或者卸载了,某些系统组件会直接罢工,那种“所有东西看起来都正常但是有个小工具突然不好使了”的灵异事件,十有八九是这个引起的。
正确的多版本方案是pyenv。它的核心思路是:把不同版本的Python编译好放在~/.pyenv/versions/目录下,然后通过环境变量和shims机制,让你当前Shell里python指向你指定的那个版本。
安装pyenv的方式官方仓库有脚本,一条命令搞定。常用命令:
pyenv install 3.12.1 # 安装指定版本 pyenv global 3.12.1 # 设置全局默认版本 pyenv local 3.12.1 # 在当前目录固定一个版本使用pyenv期间我唯一要提醒你的坑是:编译Python时系统必须装了对应版本的开发依赖,否则一半概率编译到中途直接失败。Debian系先装这些:
sudo apt install build-essential libssl-dev zlib1g-dev libncurses5-dev libreadline-dev libsqlite3-dev libffi-dev libbz2-dev至于那些说“装Python很简单,不就是下载源码./configure && make”的人,他们没告诉你的是./configure --enable-optimizations这个参数如果不开,编译出来的Python性能会差一截;而开了它,编译时间会翻倍。取舍看你自己,我通常是开着的。
5. 装完之后的事:服务注册、版本切换与依赖冲突处理
安装只是“万里长征第一步”。程序装上之后,马上要面对的是“怎么让它以后一直跑、开机自启、出错自动拉起”。这就是Linux的“程序管理”里最绕不开的一块——systemd。
5.1 systemd服务文件:把程序交给系统托管
现在几乎所有主流的Linux发行版都在用systemd管理服务和进程。你写一个.service文件放到/etc/systemd/system/,系统就能把你的程序当成一个“服务”来管,支持开机自启、异常重启、查看日志。
一个最简单的服务文件长这样:
[Unit] Description=My Custom Service After=network.target [Service] ExecStart=/usr/local/bin/myapp Restart=always User=www-data Group=www-data [Install] WantedBy=multi-user.target每个字段的含义不复杂,但有几个点得注意:
ExecStart必须写绝对路径,写相对路径系统也接受,但绝对路径更稳。User和Group指定以哪个用户身份运行,这一栏非常关键——如果你写死root,那服务一旦被人利用,就是root权限在系统里胡作非为;多数服务用专门的低权限用户跑才是安全的。Restart=always是让进程崩了自动拉起来,生产环境基本必配。
写好文件后,依次执行:
sudo systemctl daemon-reload # 重新加载服务文件 sudo systemctl enable myapp # 设置开机自启 sudo systemctl start myapp # 立即启动daemon-reload很多人会漏掉。你新增或修改了.service文件后,不执行这一句,systemd用的还是旧的内容,哪怕你改了文件它也不认。改了配置文件反复重启都无效,别忘先reload。
还有一个调试时的实用技巧:systemctl status myapp输出内容比较少,想看完整日志用journalctl -u myapp -f,它会把服务的所有stdout/stderr输出都给你实时打印出来。定位“服务起来了但没起来”这种问题时,这个命令比什么都管用。
5.2 多版本共存与优先级切换:update-alternatives
“程序管理”里另一个高频需求是版本切换。比如你的服务器上可能要在一段时间内同时支持OpenJDK 11和OpenJDK 17,或者你装了多个Python版本。这时候就要用到update-alternatives—— 它是Debian系的一套“符号链接管理器”。
它的工作原理其实很朴素:系统里维护一套多版本注册表,每个程序(比如java)在不同版本间切换时,/usr/bin/java这个链接指向谁,由update-alternatives来管理。
注册一个版本:
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-11/bin/java 1100 sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-17/bin/java 1700后面那个数字是优先级,越大表示默认越优先。切换版本:
sudo update-alternatives --config java执行后面这条,系统会列出所有已注册的java版本,你输入数字回车就切换好了。同样道理也适用于python、vim这类有多版本共存的命令。
5.3 依赖冲突的排查思路:别再“暴力重装”
装了管理程序之后,最让人头大的是依赖冲突。典型场景:程序A依赖libssl1.1,程序B依赖libssl3,你的系统里只有一个libssl包,装B的时候把libssl1.1升级成libssl3,A就罢工了。
碰到这类问题,第一反应不要是“那我强行装回去”,那样大概率会把系统的包数据库搞坏。正确排查链路是这样的:
- 先看报错,确认到底是哪个包、哪个文件冲突:
apt install xxx报错时,它通常会告诉你“将安装以下软件包”或者“以下软件包有未满足的依赖关系”。 - 问系统这个依赖现在是什么状态:
dpkg -l | grep libssl,看看装的版本是哪个。 - 查这个包被哪些包依赖:
apt rdepends libssl3,它会列出所有依赖libssl3的包。 - 判断冲突范围:如果只是A和B不能共存,看有没有替代方案——比如A的某个新版本也兼容libssl3,那升级A就行;如果没有,可能得考虑用“定期单独维护”的容器来运行A。
在Debian系上,如果你已经破坏了包数据库,还有一个“后悔药”:sudo apt --fix-broken install,它尝试把处于异常状态的依赖关系修复到能继续工作的状态。很多乱七八糟的问题,这一条命令能兜住底。
6. 卸载与清理:把安装留下的痕迹打扫干净
聊完了装和管理,最后聊卸载。别小看这一步,多少人的服务器卡、磁盘满、服务冲突,追溯下来都是“以前卸载程序没卸干净”的债。
6.1 三种卸载方式的区别
| 卸载方式 | 效果 | 适用场景 |
|---|---|---|
sudo apt remove 包名 | 删程序文件,保留配置文件 | 暂时不用,以后可能重装 |
sudo apt purge 包名 | 删程序文件和配置文件 | 彻底不想要了 |
手动删--prefix目录 | 连整个自定义安装目录一起删 | 源码编译安装的程序 |
我第一次在服务器上卸MySQL,用的是apt remove,而后重装时发现数据库配置还是旧的,跑都跑不起来,排障了半天才意识到是配置文件残留。踩过这次坑之后,我养成了一个习惯:凡是准备彻底告别一个应用,一律用purge;凡是用源码--prefix方式装的程序,直接删目录,再清掉/usr/local/bin里对应的软链接。
6.2 源码编译程序的卸载:为什么没有make uninstall
前面提到源码编译安装的程序,我推荐指定--prefix,卸载时“删目录就行”。但你可能听说过还有一个sudo make uninstall的说法。现实中这个命令很多时候是失效的——因为部分软件的Makefile压根没有实现uninstall目标,你敲了会看到make: *** No rule to make target 'uninstall'. Stop.。
所以我的经验是:编译安装前把源码包留着,源码目录里会有一个install_manifest.txt文件(部分软件会生成),里面记录了安装时复制了哪些文件到哪些路径。卸载的时候照着这个清单一个一个删,才算真正干净。如果没有这个文件,那就只能按--prefix目录整个删掉,再手动清理链接和PATH了。
6.3 残留检查:没有卸载干净的三步自查
清理完一个程序后,别急着走,花两分钟自查:
- 用
which 程序名看命令还在不在,如果还在,说明有软链接或者某处残留的可执行文件。 - 用
dpkg -l | grep 程序名或者rpm -qa | grep 程序名看包管理器数据库里还有没有记录。 - 检查
/etc下有没有以它命名的配置文件残留,数据目录(比如/var/lib/mysql)是否清理。
最后分享我踩过最深的一个坑:有一次服务器磁盘满了,排查半天,发现是一个已经被我“卸载”的应用在/var/log下留下了巨型日志文件,进程被终止了但日志文件还在疯狂增长——原来卸载程序并不会自动帮你停掉还在跑的进程。所以卸载前,一定先systemctl stop 服务名,或者pkill 进程名,否则你自己以为它没了,其实它在后台还活着,一直往磁盘里写东西。
管理Linux程序这件事,说到底是一条“文件放置——依赖登记——运行托管——卸载清理”的完整链路。你顺着这条链路把每个环节都摸透了,再去看网上任何安装教程,都不会再是你抄我抄的盲人摸象,而是能自己判断哪个步骤是合理的、哪个步骤藏着坑。遇到问题时的排查速度,也会远快于那些只会背命令的人。