
群里又有人问“怎么退出vi”我差点把刚喝的水喷出来。不是说这问题多低级而是搞Linux的人谁没在vi里卡过壳——服务器打开了配置看完了结果找不到退出方式最后只能直接关终端下次登录还提示有交换文件残留。Docker那边也常看到类似的场景镜像pull了半天容器起完就退MySQL的数个密码权限坑连环踩。两个工具一个管文本操作一个管应用打包部署几乎是Linux日常的两座必修山。这篇内容就把vi命令和Docker命令分开讲透顺手把高频报错和排查方案一起给出给正在入门和卡在瓶颈期的人做个参考。1. vi命令从入门到不卡壳1.1 模式是vi的底层逻辑vi最劝退的点是它没有一个传统意义的“文本框”。打开一个文件直接按键盘不会打字得先理解它的模式切换。默认进入的是命令模式此时键盘上每个字母都代表一条命令比如j是向下移动dd是删除整行yy是复制整行。要写字按iinsert、aappend或o在下一行插入进入插入模式这时才能正常打字。写完内容按Esc回到命令模式。还有一种模式是末行模式也就是按冒号进入用来执行保存、退出、查找替换、调整参数这一类的“文件级操作”。这其实很像电视遥控器命令模式是遥控器本身按键触发动作插入模式是输入法界面只负责打字末行模式则相当于系统菜单。很多新手的混乱就是没搞清自己当前在什么模式随便按了几下屏幕就“鬼哭狼嚎”——本质上只是触发了某些快捷键。理解这三者关系vi就算入了门。1.2 高频命令清单移动、删改、复制、粘贴vi的命令很多但日常真正高频的就那几个。我习惯把它们按动作分块记忆移动、删除、复制粘贴、撤销和查找替换。下面是走到哪都用得到的核心命令表。操作类别命令含义移动光标h j k l左、下、上、右移动移动光标w/b按单词前进 / 后退移动光标^/$跳行首 / 跳行尾移动光标gg/G跳文件首行 / 跳文件末行删除x删除光标处字符删除dd/dw删除整行 / 删除一个单词复制yy/yw复制整行 / 复制一个单词粘贴p/P粘贴到光标后 / 粘贴到光标前撤销重做u/Ctrlr撤销 / 重做查找/keyword向下查找关键字查找n/N跳下一个匹配 / 跳上一个匹配替换:%s/old/new/g全文件替换g表示全部记忆方法很简单h j k l是键盘上一排的四个键看它们的方位对应上下左右d是deletey是yank复制p是paste。删和复制都支持数量前缀比如3dd删除三行2yy复制两行。真正用熟之后手指根本不需要离开键盘这是图形编辑器给不了的效率。1.3 退出vi编辑模式新手的第一道坎作为“怎么退出vi”这个经典问题的正主这一段多说几句。如果你进vi只是看看内容不想改动直接输入:q回车。如果改完了想保存退出输入:wq或:x或者在命令模式直接连按大写ZZ。如果改乱了、想放弃保存用:q!强制退出。还有一种情况是只读文件比如/etc下的系统配置普通用户没有写权限时保存会提示readonly option is set处理方式要么是用sudo vim重新打开要么在vim里加!强行写:w!。新手最容易犯的两个错误一个是按CtrlZ想着退出。这其实是把vim放到后台挂起了进程还在终端上看起来是回到了shell但实际上shell还会提示[1] Stopped vim。这时候要么运行fg把它拉回来要么用kill %1干掉它否则下次打开文件会发现有.swp交换文件。另一个错误是直接关闭SSH终端。这样虽然进程退了但交换文件会残留再次打开文件时会弹警告。如果没保存过、确认不需要恢复可以按提示删掉对应.swp文件。1.4 寄存器操作vi里的高级剪贴板服务器上没有图形桌面的剪贴板跨行取文本就靠寄存器。vim的寄存器可以理解为一组带名字的剪贴板默认有一个无名寄存器它始终保存着最近一次删除或复制的内容还有26个命名寄存器a到z可以专门存几段内容方便穿插着粘贴系统剪贴板寄存器是在Windows版GVim或支持图形终端的vim里可以把内容送进系统剪贴板方便粘贴到浏览器或远程终端。实际操作看例子。想把第10行复制到第30行后面可以这样10G # 跳到第10行 ayy # 复制当前行到寄存器a 30G # 跳到第30行 ap # 把寄存器a的内容粘贴到光标下一行要查看当前所有寄存器里存了什么可以输入:reg。平时在SSH会话里没有CtrlC/CtrlV这套“命名寄存器”的方法非常实用。尤其改配置时在多处需要重复插入同一段内容用好寄存器比一遍遍打字快得多。有个小技巧用aY可以复制当前行到寄存器a注意大写Y相当于yy0p永远粘贴无名寄存器里保存的最近一次复制内容而不是最近一次删除内容这个细节面试偶尔会考。1.5 配置vim让默认设置顺手起来vim的默认配置对新人不太友好很多烦人问题其实可以通过~/.vimrc一次性解决。我自己的.vimrc里有这几行set nu 显示行号 set ts4 一个Tab占4个空格 set expandtab Tab转成空格 set syntax on 语法高亮 set paste 粘贴时不做自动缩进 set ignorecase 查找忽略大小写其中set paste特别重要。从网上复制一段代码贴进服务器如果不开paste模式vim会以为你在逐行输入每一行前面都加上递增缩进整段代码直接乱掉。还有中文乱码问题如果你的会话里中文显示成方块可以补上set encodingutf-8和set fileencodingsutf-8,gbk,big5。vim里还能直接调用shell命令:!ls -l可以在不退出vim的情况下执行命令:r !date会把当前时间读取到光标位置编辑Python脚本时可以用:!python3 %直接跑一遍当前文件。这些组合用熟了vim就不再是“上古编辑器”而是一个真正能当主力开发环境的东西。2. Docker命令从安装到日常部署2.1 环境准备Linux安装和Docker Desktop的虚拟化前提先把Docker本体装好。Linux服务器上最简单的方式是直接用发行版自带包Ubuntu/Debian执行sudo apt update sudo apt install -y docker.io sudo systemctl enable --now docker想用官方最新版可以用官方安装脚本国内网络慢就加阿里云镜像参数curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun装完先验证一下docker version能正常输出客户端和服务端信息就说明daemon起来了。CentOS/RHEL系要配置docker-ce仓库后安装命令略有不同但思路一样。Windows上用Docker Desktop时前提是虚拟化功能要开好。最常踩的坑就是报“virtualisation support wasnt detected”完整处理步骤我放到第3章专门讲这里只提醒一句先确认BIOS里Intel VT-x或AMD-V/SVM是Enabled状态再去看Windows的Hyper-V和WSL2功能有没有打开顺序不对装多少次Docker Desktop都一样。2.2 镜像与容器的生命周期命令Docker的核心概念就两个镜像和容器。我把“镜像≈菜谱和半成品面团”“容器≈正在烤箱里烤的那盘面包”。镜像不可变容器是镜像运行出来的实例你可以随时启动、停止、删除再重建。最常用的几条docker pull nginx:alpine # 拉取镜像 docker images # 查看本地镜像 docker rmi nginx:alpine # 删除镜像 docker run -d --name web -p 8080:80 nginx:alpine docker ps # 查看运行中容器 docker ps -a # 查看所有容器 docker logs -f --tail 200 web # 跟踪容器日志 docker exec -it web bash # 进入容器内部 docker stop web docker rm web # 停止并删除容器docker run的参数我平时默认必带-d后台运行--name起名-p做端口映射-v挂载数据-e传环境变量。其中端口映射的格式是“宿主机端口:容器端口”比如-p 8080:80就是把宿主机的8080转到容器里的80。启动后立刻用curl 127.0.0.1:8080验证能响应说明映射正常。进入容器后第一件事养成习惯看系统类型cat /etc/os-release。很多镜像精简得连bash都没有只有sh甚至没有apt、yum这些包管理器装个工具都要看镜像基于什么发行版。Alpine系用apkDebian系用aptCentOS系用yum。这点不弄清楚进容器后会满头问号。2.3 实战用Docker跑MySQL和Redis主从跑数据库比跑Nginx稍微复杂一点因为数据要落盘、密码要配置、从库要连主库。先从MySQL 8.0开始docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourPassword \ -e MYSQL_DATABASEappdb \ -e MYSQL_USERappuser \ -e MYSQL_PASSWORDAppPass \ -v /data/mysql:/var/lib/mysql \ mysql:8.0解释一下几个环境变量MYSQL_ROOT_PASSWORD设置root密码MYSQL_DATABASE会自动创建一个库MYSQL_USER和MYSQL_PASSWORD创建普通用户并授权给那个库。-v /data/mysql:/var/lib/mysql是数据持久化的关键否则删掉容器数据库文件全部消失。数据库刚起来最常见的两个访问方式docker exec -it mysql8 mysql -uroot -p mysql -h127.0.0.1 -P3306 -uroot -p # 宿主机装了mysql客户端时这里有个小坑在容器里面不能通过localhost去连宿主机因为localhost指的是容器自己。如果你某个应用容器也要连这个MySQL连接地址应该写容器名mysql8而不是127.0.0.1。MySQL 8另一个高频坑是认证插件。客户端如果是老版本连接时会报Authentication plugin caching_sha2_password cannot be loaded。解决办法有两个启动容器时追加--default-authentication-pluginmysql_native_password或者进入容器执行ALTER USER root% IDENTIFIED WITH mysql_native_password BY 新密码; FLUSH PRIVILEGES;然后再试试连接。这个坑在所有基于MySQL8的项目里几乎必踩一次。Redis主从更简单。先建一个专用网络让主从能通过容器名互相访问docker network create redis-net docker run -d --name redis-master \ --network redis-net \ -p 6379:6379 \ -v /data/redis-master:/data \ redis:7 redis-server --appendonly yes docker run -d --name redis-slave \ --network redis-net \ -p 6380:6379 \ redis:7 redis-server --appendonly yes \ --slaveof redis-master 6379注意从节点用--slaveof redis-master 6379指定主节点时用的是容器名而不是IP。这样即使IP变了从库也能正确找到主库。验证主从是否连上docker exec -it redis-slave redis-cli info replication | grep master_link如果输出master_link_status:up就说明主从同步已经建立。Redis 7也支持--replicaof参数效果一样旧版本更习惯用--slaveof。2.4 数据卷、网络和Compose编排上面已经用到了-v这就是数据卷的常见玩法。除了挂宿主机目录更“Docker原生”的方式是使用具名卷docker volume create mysql-data docker run -d --name mysql8 -v mysql-data:/var/lib/mysql mysql:8.0具名卷的好处是位置不用自己记docker volume ls能查看容器重建后数据还在。想彻底清理就看docker volume rm mysql-data。网络方面默认有bridge、host、none三种。日常用自定义bridge网络足够了docker network create mynet后docker run --network mynet起的容器彼此可以用容器名解析天然解决了容器间服务发现的问题。这对后面做微服务、组内外网隔离非常有帮助。如果服务多一条条docker run就太累了直接上Compose。一个精简的docker-compose.yml长这样services: mysql8: image: mysql:8.0 container_name: app-mysql restart: always environment: MYSQL_ROOT_PASSWORD: root123 ports: - 3306:3306 volumes: - mysql-data:/var/lib/mysql redis7: image: redis:7 container_name: app-redis restart: always ports: - 6379:6379 volumes: - redis-data:/data volumes: mysql-data: redis-data:启动命令就是docker compose up -d。这里要注意版本坑Docker的新版本直接用docker compose中间有空格老版本插件式的是docker-compose。如果你用的系统比较旧可能还需要单独安装docker-compose插件。新版compose文件其实不必写version字段但为了兼容老环境很多模板还是会保留version: 3。3. 运维场景中的疑难问题排查实录3.1 vi翻车现场粘贴、乱码、只读与交换文件vi相关的报错我见得最多的几个都很好解。第一是粘贴乱缩进代码贴进去后行首缩进越来越深原因是vim把粘贴内容当成手动输入自动缩进也参与了进来。解决办法就是前面提到过的:set paste粘贴完再:set nopaste恢复。第二是中文乱码。终端显示乱码八成是文件编码和vim编码不一致。在vim里执行:set fileencodingutf-8再:w保存一次试试如果打开就乱先:e encutf-8强制用UTF-8重新加载。持久方案是把编码设置写进.vimrc。第三是只读文件保存失败。普通用户打开/etc/nginx/nginx.conf这类系统文件编辑完:w会提示readonly option is set。不要去想用:w!硬写因为你根本没权限正确做法是退出后用sudo vim重新打开或者在vim里执行:!sudo chmod 644 %之类的补救。血的教训不要在只读文件里写了一堆期望然后用:wq失败后杀进程什么都没留下。第四是异常退出后留下的.swp交换文件。文件会在打开期间生成一个xxx.swp如果上次崩溃或SSH断线这个文件会保留。再次打开时vim会警告“Swap file already exists”你可以用vim -r file尝试恢复未保存内容确认不需要就直接删除对应的.swp文件。3.2 Docker Desktop虚拟化检测失败的全套排查Windows上装Docker Desktop报错“Docker Desktop failed to start because virtualisation support wasnt detected”的概率很高。别急着卸载重装按顺序查三处。第一确认BIOS/固件里虚拟化开关是开的。Intel平台找“Intel Virtualization Technology”或VT-xAMD平台找“SVM Mode”或“AMD-V”设为Enabled后保存重启。笔记本上这功能有些是默认关闭的这也是最常见的故障原因。第二把Windows功能打开。打开“控制面板-程序-启用或关闭Windows功能”勾选“Hyper-V”、“适用于Linux的Windows子系统”和“虚拟机平台”三项重启。注意Windows家庭版默认没有Hyper-V选项但WSL2方案可以完全替代不需要强开Hyper-V。第三用管理员身份打开PowerShell执行bcdedit /set hypervisorlaunchtype auto这个命令是告诉Windows启动时加载hypervisor。有些系统底层虚拟化组件被禁用了光重启没用必须执行这一步。执行完再重启一般就能正常启动Docker Desktop。另外如果机器里装了旧版VirtualBox或VMware它们会和Hyper-V抢虚拟化资源。优先级高的方案是升级到支持Hyper-V模式的新版本或者在用Docker Desktop时关闭其他虚拟机的“硬件加速”选项别让它们同时争抢。还有WSL2内核更新包安装失败的情况可以去官方下载wsl_update_x64.msi离线安装或者运行wsl --install之后再看Docker能不能起来。3.3 MySQL容器启动失败的常见原因docker run之后想查问题不要盲目docker inspect第一件事永远是把日志拉出来docker logs mysql8MySQL容器典型的启动失败原因有这么几个。“Cant create/write to files”这类权限错误多半是宿主机挂载目录权限不对。容器内MySQL进程是以uid 999的mysql用户运行的不认宿主机目录默认的root权限。解决办法就是给挂载目录对uid 999开放权限chown -R 999:999 /data/mysql“initialize specified but the data directory has files in it”意思是挂载目录里已经有一份不完整或旧版本的数据文件。MySQL不允许在“已存在数据”的目录里重新初始化。处理方式是清空或换一个空目录再重新docker run。端口被占用则报bind: address already in use。用ss -lntp | grep 3306看看是谁占的端口要么停掉冲突进程要么改映射宿主机端口为如33061:3306然后连接时指定-P 33061。防火墙和云安全组也不要忘有时候容器日志正常宿主机也能访问但外部就是连不上那就是安全组的坑放行对应端口即可。3.4 固定排查流程日志、端口、容器内环境Docker排障真的不靠玄学靠固定流程。我在生产环境里排查顺序基本固定docker ps -a看容器当前状态是不是Exited或反复Restartingdocker logs --tail 200 容器名拉最近日志九成问题在这步就能看到原因docker port 容器名确认端口映射是否生效docker exec -it 容器名 sh进入容器看进程是否起来、配置是否生效最后才去排查宿主机的防火墙、SELinux、云平台安全组。这套流程用了很多年MySQL起不来、Redis连不上、容器内服务没人响应基本都是五分钟内定位。关键是不要跳步更不要一上来就删容器重建。删除容器是一切数据的终点除非确认数据已经有持久化备份否则一律先看日志再动手。4. 常用命令组合与面试题实战4.1 每天都会用到的命令组合很多热搜问“Linux常用命令大全”其实比大全面更有价值的是“组合拳”。下面这几组是我几乎每天都会用的# 查端口占用 ss -lntp | grep 3306 # 实时看日志 tail -f /var/log/nginx/access.log # 看内存和磁盘 free -h df -h # 找进程并排除grep自身 ps -ef | grep java | grep -v grep # 批量替换目录下所有conf文件里的旧IP find /etc/nginx/conf.d -name *.conf -exec sed -i s/192.168.1.1/10.0.0.1/g {} \;还有一个冷门但好用的小技巧给进程改名。有时脚本里启动的Java服务进程名全是javaps里一堆分不清。可以用exec -a改掉进程名bash -c exec -a my-custom-name java -jar app.jar这样ps -ef里显示的进程名就是my-custom-name多服务混跑时一眼就能认出是哪个服务。配合Docker的便捷alias日常运维会舒服很多alias dpsdocker ps --format table {{.Names}}\t{{.Status}}\t{{.Ports}} alias dlogdocker logs -f --tail200 alias dexitdocker exec -it把这三行写进~/.bashrc每次进服务器自动生效不会再被默认的docker ps长输出刷屏。4.2 常见面试题拆解vi和Docker的细节考法面试题里出现vi和Docker考的不是“会不会用”而是“有没有真的用明白”。举几个高频例子。“如何在vi中跳转到文件末行”答案是G或gg跳文首。再问“如何把第10行到第20行移动到文件末尾”很多人会答10G再20G剪切粘贴实际最直接的是末行模式::10,20m$m是移动$表示文件末尾。想知道如何“不移动而是复制”用:10,20co$。这类命令看起来冷门但真正改过大量配置的人都知道比光标定位加粘贴快得多。Docker的面试题也一样。“如何查看一个镜像的构建历史”用docker history 镜像名。“如何清理所有已停止的容器”用docker container prune。“如何查看容器实时占用资源”用docker stats。这些命令都不难但如果平时只用run、ps、logs冷不丁被问到就会卡壳。还有一题很经典“Docker容器和虚拟机的区别是什么”核心就几句容器共享宿主机内核没有独立内核启动是秒级虚拟机有完整客户机系统隔离更强。再往下就是镜像分层、写时复制这些概念。能把命令和原理串起来深度就不一样了。4.3 个人习惯命令是练出来的最后讲点我自己的经验。学vi和Docker真的不是靠把命令大全背下来。我见过太多人收藏了一堆速查表真上了服务器依然不知所措。我的建议是先只记每天必用的15条命令然后逼自己连续一周不用图形工具改配置用vim起服务用docker run查问题用docker logs。哪怕慢也要用命令模式完成。肌肉记忆一旦形成你会明显感觉到效率比图形界面高一大截。我自己当年是在生产环境改错配置文件、被swap文件卡了十分钟之后才下定决心把vi的退出、保存、寄存器、批量替换全部练成条件反射的。现在打开一台新服务器输入vim之后脑子基本不用想手指已经自动完成跳转和改写了。Docker那边也是从“一跑MySQL就让它随便退”的懵懂状态到后来把Compose文件当成日常基础设施的一部分。这两个工具的价值不取决于命令背了多少而取决于你在真实场景中把它用得有多顺手。多踩几次坑多看看日志慢慢就会明白命令行不是门槛它本身就是效率和可控性的来源。