前两天在群里看到一位兄弟提问:用 SSH 连服务器跑一个脚本,关掉终端任务就没了,怎么让它在后台继续跑?这个问题我太熟了,几乎每隔一段时间就有新人问一次,也正好勾起了我写点东西的念头。“随笔 1(Linux)”这个标题没什么花哨,就是想把这些年和 Linux 打交道时踩过的坑、绕过的弯、想通的原理,一点点整理出来。这篇是第一篇,聊得比较杂:从装系统选镜像,到高频命令背后的系统管理逻辑,再到后台任务的三种存活方式,最后分享几个真实运维故障的排查过程。不说能从入门到精通,但至少能让正在 Linux 这条路上摸索的朋友少走几步弯路。
Linux 这东西有个特点:入门不难,用久不难,难的是出问题时不知道去哪里找答案。很多问题都不是“不会用”,而是“不知道它为什么会这样”。所以这篇随笔里我不打算只列命令,每个关键选择我都会尽量把“为什么”说清楚。能动手复现的部分也直接给了操作步骤,你可以照着试。
1. 从“装系统”说起:Linux镜像与虚拟机那点事
1.1 镜像选择:为什么我建议你直接走镜像站
很多新手第一次接触 Linux 都是先下载 ISO 镜像,然后往虚拟机里一塞就开始装。这一步看着简单,但第一个坑往往就藏在“去哪下载”上。官方站点当然最权威,可对于国内网络来说,从官方地址拖一个几个 GB 的镜像,速度起伏很大,偶尔还会中断重来。
我的习惯是优先走国内的镜像站,比如清华 TUNA、阿里云开源镜像站、中科大 USTC 镜像站、华为云镜像站。这些站点同步官方源比较及时,带宽也稳定。下载 Ubuntu 桌面版,直接进阿里云镜像的 ubuntu-releases 目录,挑对应版本号的 ISO 就行。
装完系统之后还有一件几乎必做的事:换软件源。Ubuntu 默认从 archive.ubuntu.com 拉包,这个地址在国内访问同样是时快时慢。用 sed 一次替换完,比手动打开 sources.list 一个个改要高效得多:
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak sudo sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirrors.aliyun.com/ubuntu|g' /etc/apt/sources.list sudo apt update注意我先把原文件备份了,这个习惯一直保留着。改源文件这一类操作,不管多自信,先备份一份总没坏处。另外 sed 里用的分隔符是竖线 |,不是常规的斜杠,这样可以避免和 URL 里的斜杠冲突,不用费劲转义。
换完源一定要执行 apt update,让系统重新读取索引。否则你以为换了源,实际上还在用旧索引里的地址,照样慢。
如果你的公司有局域网服务器,还可以考虑搭一个 apt-cacher-ng 或 Nexus 仓库代理,让内网所有机器都走本地缓存,外网带宽占用能下降不少。这个属于企业级玩法了,个人先换源就足够。
1.2 虚拟机安装与“经典蓝屏”的真相
“虚拟机安装 linux 系统蓝屏”这些年几乎成了热搜常客,尤其是 Windows 11 主机上装 VMware 再跑 Linux 的情况。很多人第一反应是“Linux 和 Windows 不兼容”,其实根本不沾边。
这块蓝屏的绝大多数诱因是 Windows 的虚拟化组件和 VMware 打架。Windows 11 默认开启了基于虚拟化的安全性(VBS),包括内存完整性、内核隔离这些功能。VMware 要靠 CPU 的虚拟化指令来运行虚拟机,Windows 自己也占用了这层能力,两边一冲突,虚拟机刚启动就蓝屏。
排查方法很简单:在 Windows 上按 Win+R 输入 msinfo32,看系统摘要里“基于虚拟化的安全性”是不是“正在运行”。如果是,要么去“Windows 安全中心 - 设备安全性 - 内核隔离”里关掉内存完整性,要么在“启用或关闭 Windows 功能”里取消“Windows 虚拟机监控程序平台”,然后重启。
如果你确实不想动 Windows 的虚拟化功能,那就换一条路:直接用 Hyper-V 或者 Windows Sandbox 跑 Linux,或者干脆用 WSL2。WSL2 本身就是轻量虚拟机,日常练 Linux 命令完全够用,还能和 Windows 文件系统互相访问,体验很顺。
除了蓝屏,虚拟机里装 Linux 还有一个常见问题:装完了重启进不去系统,卡在 grub 引导或者直接黑屏。这种情况多半是分区方案没选对。新机器和 UEFI 固件选 GPT 分区,老机器和传统 BIOS 选 MBR 分区。虚拟机的固件类型在创建虚拟机时就决定了,你创建的时候选了 UEFI,装系统却用传统方式分区,引导对不上自然起不来。
磁盘方面,如果只是学习用途,建议直接选“使用整个磁盘”加 LVM。LVM 的好处是以后扩容不用重新分区,在命令行里加一块虚拟磁盘,然后 lvextend 就能扩大根文件系统。生产环境我反而更推荐预留独立 /boot 分区,给内核文件一个单独的存放空间,避免某些引导器在大分区上读取异常。
2. 命令不是背出来的:高频命令背后的系统管理逻辑
2.1 三个高频命令的常见误区和正确姿势
先说删除文件夹命令。每次看到新手问“Linux 怎么删除文件夹”,我都会多一句嘴:你确认要删哪个目录、删完之后要不要恢复?rm -rf 是这个问题的标准答案,但也是最容易出事故的答案。它不像 Windows 回收站那样有个后悔机会,删了就真没了。
我在自己的机器上给 rm 设置了一道保险:alias rm='rm -I'。-I 会在删除三个以上文件或者递归删除时弹出确认提示,比 -i 的逐个确认打扰少,又比裸奔安全得多。你要是觉得自己容易手滑,还可以把 trash-cli 装上,用 trash 命令代替 rm,删除的文件先进回收站,后悔了还能捞回来。
再说新建用户。新手经常用 useradd,但创建完发现用户没有家目录、登录还报错,原因很简单:useradd 是底层工具,默认行为很保守,不建家目录也不设置 shell。面向普通用户的封装命令是 adduser,它是 Perl 写的交互脚本,会引导你设置密码、建家目录,一步步来。如果你必须在脚本里用 useradd,也更可控,至少把 -m -s /bin/bash 加上:
sudo useradd -m -s /bin/bash admin sudo passwd admin-m 建家目录,-s 指定登录 shell。这个细节面试也常考,两者区别几乎默认成了送分题。
还有一类高频操作是重命名文件。Linux 里没有 rename 命令对应的图形界面操作,重命名就是 mv。mv old.txt new.txt 看着简单,但覆盖同名文件时非常安静,不给你任何提示,直接写掉了。我建议在重要目录里用 mv 之前先 ls 看一眼目标名是不是已经存在,或者加上 -i 参数让它在覆盖前问一句。
顺带提一下 shell 脚本里最常见的安全问题:变量忘记加引号。比如你写了 rm -rf $DIR,当 DIR 为空时,命令变成 rm -rf,配合 -f 参数会尝试删除所有它认为能删的文件。这个坑很多人踩过,教训就是:脚本里所有变量加双引号,除非你明确知道自己在干什么。
2.2 从命令到底层原理:建立你的Linux认知地图
光会敲命令,碰到“为什么”还是会懵。我见过不少能用 Linux 日常办公的人,遇到一次进程消失、端口被占用就束手无策,因为他们脑子里没有一张“Linux 地图”。
这张地图的核心是理解进程和文件描述符。你在终端里敲的每条命令,本质上都是一个新的进程,由 shell 进程 fork 出来再 exec 成目标程序。每个进程都有 PID,父进程是 PPID。用 ps -ef 就能看出来谁生了谁。理解了这一层,你就明白为什么“终端一关,任务就没了”——因为你关掉的是父进程,它底下那些子进程默认会在终端退出时收到挂断信号,跟着一起走。
另一个绕不开的概念是文件系统挂载。很多新手以为 / 就是一个磁盘分区,其实 / 只是一个挂载点,真正的数据存在某个分区或设备上。你用 df -h 看到的不是“目录有多大”,而是“这个挂载点所在文件系统有多大”。理解挂载之后,mount 命令、/etc/fstab 自动挂载、U 盘插入后去哪找,这些问题就会迎刃而解。
如果你想往内核方向深挖,零基础可以先读《鸟哥的 Linux 私房菜》建立整体操作观念,再配合《Linux 内核设计与实现》理解进程调度、内存管理、文件系统这些核心模块。只靠操作积累很难走到内核层面,但反过来,没有操作经验直接啃源码也容易失去方向。我的建议是:先会用命令解决实际问题,再带着问题去读原理。
Linux 面试里频繁出现的硬链接和软链接、进程和线程的区别、上下文切换是什么,都在这张地图里。硬链接是同一个 inode 的多个名字,软链接是一个存着路径的独立文件。进程是资源的拥有者,线程是调度单位。这些不是背诵概念,理解了它们,很多排查思路会自动冒出来。
3. 让任务在后台安稳运行:终端断了也要继续工作的三种做法
3.1 nohup 与 &:最朴素的“断连续跑”
回到开头那位兄弟的问题:SSH 登录服务器,跑一个脚本,关掉终端任务就没了。为什么?因为 SSH 会话结束时,系统会给这个会话关联的所有进程发送 SIGHUP 信号,进程收到默认动作就是退出。
nohup 这个名字已经说明了一切:no hang up,忽略挂断信号。用法很简单:
nohup bash train.sh > train.log 2>&1 &这里有三件事要拆开看。& 是把命令放到后台执行,shell 不用等它结束才返回。nohup 让它忽略 SIGHUP,终端断开后进程还能继续跑。> train.log 2>&1 是把标准输出和标准错误都写进日志文件,否则你啥也看不到。
2>&1 的写法有顺序讲究。它的含义是“把标准错误重定向到标准输出当前指向的地方”。所以 2>&1 必须放在重定向文件之后,写成 1> train.log 2>&1 才对。如果写成 2>&1 1> train.log,标准错误会先指向终端,再把标准输出指向文件,结果报错信息依然打在终端上,到断线时还是会丢。
跑起来之后,用 tail -f train.log 看进度。注意 nohup 不是万能的,它只能挡挂断信号。你要是手动 kill 进程,进程该退还是退。
3.2 setsid 与 disown:更进一步脱离控制终端
如果说 nohup 是“忽略挂断信号”,那 setsid 就是“彻底退出当前会话”,让进程自立门户,和原来的控制终端再没关系。这在某些交互敏感的场景里比 nohup 更稳,因为进程不再属于原会话,终端退出时信号根本找不到它。
用法更直接:
setsid bash train.sh > train.log 2>&1 &还有一个容易混淆的命令叫 disown。它和 nohup 解决的问题不太一样。disown 是把作业从 shell 的作业表里移除,让 shell 在退出时认为“这个作业跟我无关”。先让任务跑起来:bash train.sh &,然后立即 disown 或 disown -h。加了 -h 是只移除挂断关联,不停止任务。实际用下来,disown 适合那种你已经启动但忘了加 nohup 的补救场景。
这三者的对比我给你整理一下:
| 方式 | 断连后继续跑 | 是否脱离控制终端 | 典型场景 |
|---|---|---|---|
| nohup + & | 是 | 否,只忽略挂断 | 简单脚本、一次性任务 |
| setsid + & | 是 | 是,完全脱离 | 长时训练、守护类脚本 |
| disown | 是 | 否,只从作业表移除 | 忘了加 nohup 时的补救 |
3.3 systemd 与 tmux:生产环境下的现代化方案
nohup 和 setsid 适合临时任务,但正经生产环境里,我更推荐用 systemd 来管理后台常驻任务。好处很多:开机自启、崩溃自动重启、日志统一收集、权限隔离。
不想写 unit 文件的话,systemd-run 可以快速起一个临时服务:
sudo systemd-run --unit=myjob --description="my background job" /opt/scripts/deploy.sh想长期管理,就写一个 unit 文件放到 /etc/systemd/system/ 下。我给你一个可用的模板:
[Unit] Description=my long-running service After=network-online.target Wants=network-online.target [Service] Type=simple User=deploy ExecStart=/opt/scripts/my_task.sh Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target写完后执行 sudo systemctl daemon-reload,再启动服务。日志不用自己维护,journalctl -u myjob -f 直接看。Restart=on-failure 是崩溃自动重启的核心配置,RestartSec=5 控制重启间隔,避免故障时疯狂重启把机器拖垮。
如果你跑的是交互式任务,比如编译、调试、需要人工按键的程序,那 systemd 帮不上忙,这时候该用 tmux。tmux 创建的是一个持久会话,会话和终端分离。你在会话里跑任何程序,随时可以按前缀键再按 d 分离会话,然后退出 SSH,过一会儿再连上,用 tmux attach 重新接回去,程序还在原来的状态。
常用的三连我写在这里:
tmux new -s work # 创建名为 work 的会话 tmux detach # 分离会话(前缀键 Ctrl+b 然后按 d) tmux attach -t work # 重新接入会话你可以把 tmux 理解成一个“挂在系统上的虚拟窗口”,每个会话都是独立的进程树,终端只是它的一个观察窗口,窗口关了也不影响会话本身。
4. 运维故障案例实录:三次值得记住的排查经历
4.1 磁盘满了,df却显示还有可用空间
有一次线上告警说磁盘使用率超了,我 ssh 上去一看 df -h,根分区还显示 59% 可用,但新建文件却报 No space left on device。当时第一反应是 df 输出有问题,后来想起来还有个 df -i。
df -i 看的是 inode 使用率。磁盘空间还有,但 inode 节点被耗尽了,同样写不进新文件。inode 就是文件系统的“户口本”,存文件的基本属性和数据块位置,每个文件或目录至少占一个。小文件特别多的目录最容易把 inode 耗尽,比如邮件队列、缓存目录、临时文件目录。
定位方法是用 find 统计各目录文件数量:
find / -xdev -printf '%h\n' | sort | uniq -c | sort -k1 -rn | head -20这条命令先打印每个文件的父目录,然后统计每个目录下文件数量,快速找出最可疑的目录。
还有另一个“磁盘满但 df 正常”的经典场景:文件被删了但进程还在用。你在日志目录里用 rm -f access.log 删掉一个大日志,df -h 却看到空间没释放。那是因为有进程仍然持有这个文件的操作句柄,文件虽然没名字了,但数据块还占着。用 lsof 找出来:
lsof | grep deleted看到哪个进程还握着已删除文件,重启或 reload 那个进程,空间才会真正释放。这也是为什么生产环境的日志一定要配合 logrotate 做轮转和清理,直接 rm 不仅不优雅,还可能引发这种空间不释放的假象。
4.2 时间不同步引发的“灵异事件”
有段时间某台服务器上 HTTPS 请求频繁报证书校验失败,数据库连接也时不时断。查了半天,最后发现是系统时间慢了将近 8 分钟。证书有效期、Kerberos 认证、部分数据库协议对时间偏差非常敏感,差几分钟就可能直接拒绝服务。
Linux 下查时间状态用 timedatectl:
timedatectl status它会显示系统时间、时区、RTC 时间,以及 NTP 是否激活。如果 NTP synchronized 显示 no,那就需要手动同步或配置自动同步服务。
现在主流做法是用 chrony 替代老的 ntpdate。chrony 同步速度更快,也不会像 ntpdate 那样直接跳跃时间。老式 ntpdate 在时间差较大时会把系统时间跳过去,这个“跳变”可能会让正在运行的进程产生异常行为,比如计时器错乱、调用了时间函数的程序计算出负数。
生产环境推荐的做法是安装 chrony,并确保服务开机自启:
sudo apt install chrony sudo systemctl enable --now chrony chronyc trackingchronyc tracking 会显示当前时间偏差和步进情况,运维排查时这个命令很实用。别小看时间同步这件事,线上很多“查不出原因”的间歇性故障,最后都归结到时间偏移上。
4.3 管道与进程间通信的两个经典坑
管道是 Linux 进程间通信最基础的方式,但很多新手在使用管道时对它的行为并没有真正理解。最典型的就是 SIGPIPE 信号问题。
举个例子:cat hugefile.log | grep "ERROR" | head -n 10。head 只看十行就退出了,grep 和 cat 还在往管道里写数据。当管道写端继续写已无读者的管道时,内核会发送 SIGPIPE 信号给写进程,默认行为是终止进程。这就是为什么复杂管道里有时会看到上游命令报 Broken pipe 错误。如果程序忽略了 SIGPIPE,那它就会一直循环写,白白消耗 CPU。
另一个面试和实战都常踩的坑是单个管道缓冲区容量有限。管道缓冲区在 Linux 上默认通常是 64KB 左右,当写方写入速度远快于读方读取速度时,写操作会阻塞,直到读方消化数据。如果读方也卡在不合理的地方,双方可能互相等待,形成管道死锁。
解决这类问题没有银弹,关键是理解进程之间是“流式配合”关系,速度不匹配时总要有人阻塞。排查时可以用 strace 跟踪进程在哪个系统调用上卡住:
sudo strace -p 12345 -f -e trace=read,write看到进程长期停在 write 系统调用上,基本就是管道对端没有在消费数据。
除了管道,信号也是常见的进程间通信方式。信号处理函数里切忌做重量级操作,比如锁操作、内存分配、打印日志。因为信号随时可能打断进程的正常执行,和主流程抢锁会直接死锁。正确做法是信号处理函数只设置一个标志位,主循环里检查并处理。
共享内存是速度最快的进程间通信方式,但也最容易出同步问题。多进程写同一块共享内存,必须配合信号量做互斥。不做的后果是数据覆盖、读到半截状态,现象极其诡异。跨主机通信就得上 socket,那是另一套体系了,对应的排查工具也从 strace 换成了 tcpdump 抓包分析。
5. 从热搜词看Linux圈的几个热点
5.1 国产Linux发行版的现状观察
这几年国产 Linux 发行版的讨论热度明显上来了。openEuler、Anolis OS、银河麒麟、统信 UOS 这些名字频繁出现在社区里。我在虚拟机里装过其中几款,整体感受是:系统本身的完成度已经相当不错,桌面环境、软件包管理都能正常使用,日常办公文档处理基本不会有障碍。
但客观说,生态还是个慢慢补课的过程。Linux 圈子有个经典问题叫“最后两厘米障碍”——系统能起来、驱动能装上、图形界面能显示,但打印机驱动、网银插件、专业行业软件这些细碎的东西,依然需要兼容层或替代方案。很多国产发行版也意识到了这点,做了不少 Windows 应用兼容的工作,比如打包了 wine 的定制版本,或者提供安卓应用运行环境。
从开发者角度看,国产发行版里的 openEuler 和 Anolis OS 更吸引我。它们都有活跃的社区、成体系的文档,还有面向云原生场景的优化。CentOS 停更后,不少公司把业务迁移到这些兼容发行版上,转移成本比想象中低。选型的时候主要看两件事:包管理方式是否顺手、厂商支持周期能承诺多久。技术底子其实已经不是一个不可逾越的门槛了。
5.2 嵌入式Linux项目的学习路径建议
嵌入式 Linux 是很多后端、运维工程师转岗时盯上的方向,热词里“嵌入式linux项目”频繁出现。如果你想入门,我的建议是先搞清楚嵌入式 Linux 和桌面 Linux 的根本差异:桌面 Linux 跑在通用硬件上,驱动由内核帮你枚举加载;嵌入式 Linux 跑在特定 SoC 上,很多东西需要你交叉编译并定制。
学习路径可以按这个顺序走:先巩固 C 语言和基础 Linux 命令,然后学交叉编译工具链的用法,比如 arm-linux-gnueabihf-gcc。接着接触 u-boot 和内核编译,理解引导过程,再学根文件系统制作,用 Buildroot 或者 Yocto 把整个系统打包出来。最后才是设备树和驱动开发。
硬件方面不需要一上来就买高端开发板,百元左右的全志、瑞芯微核心板足够入门。很多国产 SoC 开发板资料丰富,官方 SDK 和社区文档都很全,遇到问题也能搜到同类解法。嵌入式学习最忌讳只看不动手,一块板子、一个串口调试线、一个 TF 卡,就能把整套流程跑通。
工具链的选择上,新人建议直接从 Buildroot 开始,它帮你省掉了很多手工定制根文件系统的琐碎工作,镜像配置清晰,生成结果可直接烧录到板子。等你把 Buildroot 用熟了,再挑战 Yocto 会更从容。
5.3 关于安全测试系统Kali Linux的一点提醒
热搜里出现“kail linux中文版安装”,我猜是指安全测试领域常用的 Kali Linux。它是基于 Debian 的发行版,预装了大量网络分析和渗透测试工具,在安全圈子里几乎是标配。
这里我必须多说一句:安全测试工具只在你自己拥有或获得书面授权的系统中使用。拿别人没授权的系统练手,这不是技术问题,而是法律和道德问题。想学习的话,可以在虚拟机里搭几个靶场环境,或者用 Docker 起一些脆弱的应用做实验,一样能练到技能。
安装方面,Kali 官方支持镜像安装和虚拟机镜像。新手不建议把它当作日常使用的主力系统,而是跑在虚拟机里按需启动。虚拟机快照功能尤其好用,实验环境搞坏了回滚一个快照就行,不用重装。
另外它的中文名经常被写成“kail”,字面拼写是 K-a-l-i,跟“卡利”同音。搜索安装教程时用英文名 Kali Linux 能搜到更多准确的结果。
随笔收尾:分享几个长期受用的小习惯
写到最后,我分享几个自己在实际工作中养成的习惯。第一个是操作前先备份,不管是配置文件还是数据库,改之前留下退路总没错。第二个是重要命令前先加 echo 试跑一遍,特别是 rm、mv、dd 这类不可逆操作,先看它会执行什么,心里有数再动手。第三个是日志一定要集中管理,容器时代就用 Docker 的 json-file 驱动加 log rotate,或者直接用 journald,别让应用自己往无限增长的文本文件里写。
还有一个算是私藏的小技巧:遇到不确定的命令参数,先敲 man 命令查手册,比在搜索结果里翻二手解释快很多。比如 man rm、man grep、man ssh,手册里就写着各种边界行为和退出码含义。很多谜底其实都在系统自带的文档里,只是我们太习惯先去找搜索引擎了。
Linux 这条路没有尽头,但好在每天都能学到一点新东西。这篇随笔里的内容大部分来自我自己深夜排查故障和重装系统的经历,写下来也算是给自己做个存档。下一篇随笔想聊聊 shell 脚本的稳健性设计,包括 set -euxo pipefail 那些事儿,如果你们感兴趣,我们下篇接着聊。