Linux 学到第13篇(中),最容易掉进去的坑是:命令背得滚瓜烂熟,一遇到真实场景还是手忙脚乱。这篇文章不打算再列一遍常用命令大全——那个你随时可以 man 或者查 help——我想把过去三个月被问到最多的几类问题集中拆一遍:新建用户时那些奇怪的参数、find 为什么老是删错文件、scp 大文件中断后怎么办、Linux 里装 Python/Docker/Nginx 时被官方文档省略的细节,以及嵌入式项目里多进程和 TCP 数据流到底该怎么看。适合刚接触 Linux 一两年、能跑通基本操作但遇到实际项目还会卡的读者。
这个系列写到第十三篇,我最大的感受是:Linux 知识不是线性增长的,而是螺旋上升的。很多命令第一遍用觉得很简单,第二遍用以为自己会了,第三遍在生产环境里出问题才发现根本没理解。所以这篇“中篇”聚焦的都不是什么冷门技巧,反而是高频场景里的细节。每一段都会解释背后的原因,而不是只甩命令。你完全可以把它当一篇独立的 Linux 运维实战笔记来读,不需要回头看前面的内容。
1. 新建用户这件事,比你想的更容易出错
1.1 useradd 和 adduser 的差别不在名字长短
很多新手在“Linux 新建用户”时,先搜到 useradd,结果执行完发现没有家目录,登录还报错。这不是 useradd 坏了,而是它作为一个底层工具,默认行为就是“只创建用户记录”,不做任何额外的用户体验优化。你要的登录 shell、家目录、邮箱目录,它都不会主动帮你建。而 adduser 是 Perl 写的交互式脚本,跑起来会一步步问你密码、全名、房间电话,然后自动把家目录、.bashrc、.profile 这些骨架文件都准备好。
我自己的习惯是:临时建用户用 adduser,写自动化脚本用 useradd。因为脚本里没法交互,useradd 配合参数反而更可控。比如要建一个带家目录、指定 shell 和用户组的用户:
bash复制sudo useradd -m -d /home/deploy -s /bin/bash -G www, docker deploy
sudo passwd deploy
这里 -m 是创建家目录,-d 指定家目录路径,-s 指定登录 shell,-G 指定附加组。注意 -G 是附加组,主组用 -g。如果不加 -m,家目录不存在,后面 SSH 登录会因为找不到 .ssh 目录而出现各种奇怪问题。另外一个常见坑是:useradd 创建的用户 UID/GID 从 1000 开始自动递增,如果你想在多个服务器之间保持 uid 一致,必须显式用 -u 指定。否则 NFS 共享目录时会出现“明明都是同一个用户名,权限却对不上”的诡异现象。
除了创建,用户密码策略也值得说一句。很多人建完用户 set 一个密码就完事了,但黑客可能用弱口令爆破。生产环境至少应该用 chage 设置密码过期时间,比如强制 newuser 每 90 天改一次密码:
bash复制sudo chage -M 90 newuser
查看用户密码状态用 sudo chage -l newuser。这些命令虽然不常出现在“常用命令大全”里,但真实运维中比一次次登录 shell 更重要。
1.2 权限分配:为什么“提权”漏洞大多出在 sudoers 上
“Linux 提权”这个词经常出现在安全文章里,但站在运维角度,我更关心的是怎么让普通用户刚好能完成工作,又拿不到 root。换句话说,提权攻击之所以频繁发生,绝大多数是因为权限给得太宽。很多人图省事,把开发账号直接扔进 sudo 组,甚至给出 NOPASSWD: ALL,这等于把大门钥匙复制了一堆。
正确做法是用 visudo 编辑 /etc/sudoers 或 /etc/sudoers.d/ 下的文件,把权限收敛到具体命令。比如给一个操作 nginx 的用户只允许重启和检查配置:
bash复制deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx, /usr/bin/nginx -t
这样他没办法 sudo bash,也没办法改其他服务。还有一点容易被忽略: sudo 的权限和用户组绑定,如果用户被从组里移除,已经建立的连接里的 sudo 权限可能也被回收,但新登录要重新加载。所以排查权限问题时,不要只盯着 sudoers 里的文本,先执行 groups 和 id 看看用户当前属于哪些组。组配置是提权漏洞最常见的入口,尤其在多团队共用服务器时,把一堆人塞进同一个组,等于权限被均摊到最不安全的那个人身上。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 文件查找和删除:find 的优先级、rm 的保险丝、scp 的替代品
2.1 find 的表达式有优先级,别靠肉眼猜
Linux 命令大全里总少不了 find,但真正把它用明白的人不多。find 的表达式不是简单的“条件从左到右挨个执行”,而是有隐含的“与、或、非”优先级,类似编程语言里 && 高于 ||。比如这样一条:
bash复制find /data -name "*.log" -type f -mtime +7 -size +100M
它的意思是:在 /data 下找扩展名为 .log、是普通文件、修改时间超过 7 天、大小大于 100MB 的文件。中间默认是“与”的关系。如果你用了 -o(或),就必须用圆括号把条件分组,否则很多人会把逻辑搞反。find 的括号在 shell 里要转义,写成 ( ):
bash复制find /data -type f \( -name "*.log" -o -name "*.txt" \) -mtime +7
另一个高频错误是 -exec 不加保护直接删。比如:
bash复制find . -name "*.bak" -exec rm {} \;
如果路径里有空格或特殊字符,{} 传过去可能被错误解析。虽然现代 find 会处理,但更安全的做法是先用 -print 预览结果,确认范围没问题后再执行。我自己的习惯是:需要批量操作时先跑 find ... -print,把结果重定向到文件,人眼扫一遍,再决定下一步。批量删除文件最好的方案也不是 rm 配合 find,而是把待删列表用管道丢给 xargs,并加 -d '\n' 按行处理。
2.2 rm -rf 删错之后,能救回来吗
“Linux 删除文件夹命令”这个话题下面,永远会有人问 rm -rf 删错了能不能恢复。说实话,普通文件系统上用 rm 删掉后很难恢复,尤其是到了 SSD 和开启了 trim 的现代 Linux 系统,删掉的数据基本救不回来。所以我对新手的建议是:能不用 rm -rf 就不用,至少先学会几个保险手段。
第一个手段是把 rm 替换成 trash-cli,类似 Windows 的回收站。装好之后用 trash-put 删除文件,还会留在回收站目录,可以反悔。第二个手段是改写 shell 别名,在 .bashrc 里加一行 alias rm='rm -I',这样删除文件前会多一次确认,尤其对 rm -rf 这类危险命令,-I 参数会提示你是否确认。第三个手段是重要目录做快照,比如用 btrfs 的子卷快照,或者定期 rsync 到备份盘。这和 Windows 上“回收站+还原点”的思路一样。
如果一定要删空目录,用 rmdir,它只能删空目录,误删的概率低很多。如果目录里文件太多需要快速清理,比如删除半年以上的日志,与其 rm -rf 整个目录,不如先 find -delete。注意 find -delete 会在找到文件时直接删除,不用经过管道,效率更高,但同样要先核对路径。我自己在清理磁盘时都会执行 df -h 和 du -sh * 先看空间分布,再动手删,避免“目录看着不大,删完系统崩了”的尴尬。
2.3 scp 的大文件中断问题,用 rsync 解决
scp 命令是 Linux 下最常用的跨机器传文件工具,语法简单:scp file.txt user@host:/path/。但它有个致命弱点:传大文件时如果网络抖动中断,整个传输就失败了,不会有断点续传。这时候用 rsync 才是正确姿势。rsync 不仅能断点续传,还能做增量同步,只在本地和目标之间传输变化部分。
常用的一条同步命令:
bash复制rsync -avz --partial --progress /data/backup.tar.gz user@host:/backup/
其中 --partial 是关键,它让 rsync 保留未传完的临时文件,下次继续从断点传,而不是从头开始。-a 是归档模式,保留权限、时间戳等;-z 是传输时压缩,适合文本类文件,如果你在传已经压缩过的 tar.gz,加 -z 反而浪费 CPU;-v 是输出详细日志;--progress 显示进度。如果你要传整个目录,注意源路径末尾斜杠的差别:rsync -avz /src/ user@host:/dst/ 表示同步 src 目录内容到 dst,而 rsync -avz /src user@host:/dst 会把 src 目录本身也放到 dst 下。这个斜杠问题几乎每周都有人踩。
另外,如果你只是 Windows 和 Linux 之间共享文件,没必要用 scp 来回拷贝,直接布置 Samba 共享更舒服。局域网内挂载共享目录后就像本地文件一样操作。不过跨公网同步还是 rsync + SSH 最稳,还能搭配 SSH key 免密。注意 rsync 通过 SSH 传输时,本地要装 openssh-clients,远端也要有 sshd 在跑,否则会报 connection refused。
3. Python、Docker、Nginx:部署时被省略掉的关键细节
3.1 源码编译安装 Python:configure 之后还有个 altinstall 陷阱
很多服务器自带的 Python 版本比较老,项目需要新版本时,大家第一反应是“Linux 系统安装 Python”,然后下载源码编译。编译这一步本身不复杂,但有一个细节官方文档写得很含蓄:要用 make altinstall,而不是 make install。make install 会替换 /usr/bin/python3 这个系统软链接,而系统很多底层工具,比如 yum、firewalld,都是依赖系统自带 Python 的。你一旦把默认 Python 换成新版本,可能导致整个包管理工具崩掉。
正确的源码安装流程是:
bash复制wget https://www.python.org/ftp/python/3.11.9/Python-3.11.9.tgz
tar xf Python-3.11.9.tgz
cd Python-3.11.9
./configure --enable-optimizations --with-ensurepip=install --prefix=/usr/local/python311
make -j$(nproc)
sudo make altinstall
--enable-optimizations 会启用 PGO 优化,编译时间会长不少,但解释器性能更好;--with-ensurepip=install 会顺便装上 pip;--prefix 指定安装目录,避免污染系统目录。装完之后不会覆盖系统 python3,而是在 /usr/local/bin 下生成 python3.11。运行时用 python3.11 或建一个 venv 虚拟环境来用。最稳妥的做法是:给项目建虚拟环境,不要直接往全局装第三方包。一个项目一套依赖,升级 Python 或迁移服务器时不会乱成一锅粥。
这里的另一个隐形坑是依赖库缺失。编译 Python 之前,系统里要装好 zlib、libffi、openssl-devel 这些依赖,否则后续安装 pip 或使用 ssl 模块时会报错。发行版的包名不同,但大概就是:
bash复制sudo apt install -y build-essential zlib1g-dev libffi-dev libssl-dev libbz2-dev libreadline-dev
顺序上来讲,先把这些包装好,再跑 ./configure,否则后面补装完还要重新 configure 一次,白等半天编译时间。
3.2 Docker 装完先做三件事,否则后面一定后悔
Linux 上安装 Docker 本身不复杂,配置一下软件源然后 apt install docker-ce 就行。但装完之后如果直接开始用,很快会遇到三个问题。
第一是镜像拉取慢。很多人以为是因为网络差,其实是因为 Docker 默认从国外官方仓库拉镜像。解决办法是配置镜像加速器,在 /etc/docker/daemon.json 里写好 registry-mirrors,然后重启 Docker。例如配置腾讯云、阿里云等国内可访问的镜像加速地址。这里要注意:改完 daemon.json 后需要 systemctl daemon-reload && systemctl restart docker 才会生效,而且镜像加速只影响 docker pull,不影响镜像仓库的登录认证。
第二是当前用户没有权限执行 docker 命令。你可能会遇到明明已经安装成功,执行 docker ps 却提示 permission denied。这是因为我之前用 sudo 装的 docker,docker 命令需要在 root 权限下访问 /var/run/docker.sock。最简单的解法是把用户加入 docker 组:sudo usermod -aG docker $USER,然后重新登录。但这个操作也意味着该用户等同于拥有了 root 权限,因为 docker 本来就能通过挂载宿主目录实现任意文件访问,所以不要把不信任的用户加进 docker 组。
第三是容器日志无限增长。默认情况下 Docker 的 json-file 日志驱动不会限制日志大小,一个健忘的应用一天能写几十 GB 日志,直到磁盘爆满。运维多年的经验告诉我,daemon.json 里日志限制一定要提前配好:
json复制{
"registry-mirrors": [""],
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
}
}
这样每个容器日志文件能自动滚动,单文件不超过 50MB,最多保留 3 份。不然等磁盘告警再处理,可能得手动删容器日志或重开容器,相当麻烦。
3.3 Nginx 的两个默认配置,改完才敢上生产
Linux 安装 nginx 可以通过 apt 或 yum,也可以用源码编译,编译参数各有一套。但对大部分项目来说,用发行版仓库安装然后精细调整配置就足够了。安装完成后,不要急着把服务跑起来,至少改两个配置。
第一个是 server_tokens off。默认情况下 Nginx 错误页会带版本号,等于告诉攻击者你用的是哪个版本,方便他找对应漏洞。把 server_tokens 设为 off,错误页只显示 nginx 而不是 nginx/1.24.0。第二个是 worker_processes 和 worker_connections。Nginx 使用的是事件驱动模型,worker_processes 通常设为 auto,让它自动匹配 CPU 核数;worker_connections 决定每个 worker 能维持多少并发连接。经典公式是:最大并发连接数 = worker_processes * worker_connections。如果你的机器是 4 核,worker_connections 设为 10240,理论上能扛 4 万连接,真实项目还要受限于文件描述符、内核参数和上游服务。
还有一个小细节是 keepalive_timeout。默认 65 秒对某些短连接应用太浪费,可以调低到 20 秒,让空闲连接更快释放;但如果你是 WebSocket 长连接,需要反向调大或单独配。生产环境改完配置,先执行 nginx -t 检查语法,再执行 systemctl reload nginx 平滑重载,千万不要用 systemctl restart nginx,除非你允许瞬间断连。这些都是老生常谈,但每一次线上事故都有人倒在这里。
4. 从命令到工程:嵌入式 Linux、多进程通信与 TCP 数据流
4.1 嵌入式 Linux 项目中的命令行:部署比调试更重要
“嵌入式 Linux 项目”这个关键词下面,经常有人以为要到内核源码里翻东西。实际接触嵌入式项目后你会发现,日常用到命令行的场景更多是部署和打包。交叉编译好的内核镜像、设备树、根文件系统,需要通过 SSH、NFS 或者 TFTP 传到目标板上,然后启动、挂载、跑服务。这里最常用的还是 rsync 和 scp,目标板上空间有限,传文件时别把庞大编译目录整个丢进去,而是按需拷贝产物。
我自己调试 ARM 开发板时,常用的一套流程是:本地交叉编译生成可执行文件,用 scp 传到板子的 /usr/bin 或 /opt/app,接着远程执行。板子上不一定有 gdb,所以日志系统就特别重要,启动参数里打开 verbose,代码里按模块打 tag,再配合 dmesg 看内核打印。很多嵌入式问题看起来像应用崩溃,实际是设备树配置错误导致外设驱动没加载。这时候靠命令排查:ls /sys/class/ 看有没有对应设备节点,cat /proc/interrupts 看中断有没有注册,cat /sys/kernel/debug/ 里的信息看驱动状态。命令行不是万能的,但能帮你把问题范围一层层缩小。
4.2 多进程通信:先选模型,再选框架
“Linux 多进程通信框架”听起来是个很高大上的主题,但万变不离其宗:管道、消息队列、共享内存、信号、socket。选择依据不是哪个新潮,而是场景。父子进程之间简单协作,管道就行;多个不相关进程之间传结构化小数据,消息队列更顺手;需要大吞吐量共享数据,共享内存加信号量是性能之王;跨机器通信,只能走 socket。
我自己在写一个采集程序时,数据采集进程和上报进程就是父子关系,需求很简单:子进程把采集到的数据发给父进程。一开始我用了消息队列,代码能跑,但结构复杂。后来改成管道,逻辑清晰多了。管道示例:
c复制int pipefd[2];
pipe(pipefd);
if (fork() == 0) {
close(pipefd[0]);
write(pipefd[1], "hello", 5);
exit(0);
}
close(pipefd[1]);
read(pipefd[0], buf, 5);
这里要记得关闭不用的管道端,否则读端会一直阻塞等待。多进程通信框架本质上是对这些原语的封装,帮你处理了创建、同步、释放的细节。但我个人的建议是:业务简单时直接用系统调用,别上来就套一个重量级框架。等你的进程数超过五六个,数据流出现复杂分支,再考虑用成熟的进程管理工具或消息中间件,否则代码会变成无法调试的“框架面条”。
4.3 TCP 数据流走读:send 返回并不代表对方收到
这个热词是“linux tcp协议栈数据流走读csdn博客”,说白了就是你在 Linux 上调网络程序时,要知道一次 send 调用背后发生了什么。应用层调用 send 后,数据先拷贝到内核 socket 的发送缓冲区。TCP 协议栈根据拥塞窗口和接收窗口决定能发送多少,然后封装成段,交给 IP 层,IP 层加路由和分片信息,再交给网卡驱动,最后通过中断告诉驱动发送完成。
这里最关键的一个点是:send 返回成功,只代表数据被内核接收并放进了发送队列,绝不代表对端应用已经读到。如果你要保证业务层面“对方收到了”,必须在应用层做确认,或者用带 ACK 语义的同步机制。排查网络问题时,不要只看应用日志,要用 tcpdump 在发送端和接收端同时抓包,对比包序号和时间戳。比如抓包命令:
bash复制tcpdump -i eth0 -nnA 'tcp port 8080'
抓包文件可以保存成 pcap 再用 Wireshark 分析。你会看到 TCP 三次握手、数据段、ACK 的交互过程。理解了这条数据流,你会明白为什么 netstat 里的 Send-Q 一直不为零就是发送缓冲区满了;为什么 Recv-Q 堆积说明应用读得不够快;为什么 TCP 优化调的不是“发送速度”,而是窗口、缓冲区和拥塞算法。这些知识面试会问,线上排查更常用。
5. 故障排查和面试:这些场景才是考基本功的地方
5.1 9090 端口被占用?这样一步步找到真凶
服务器上某个服务起不来,报错“bind: Address already in use”,对应的热词是“linux的9090端口什么再用”。新手第一步往往是百度,其实一条命令就能看到:
bash复制ss -tlnp | grep 9090
ss 是 netstat 的现代替代品。如果没显示进程,但端口确实被占,加上 sudo 再试,因为非 root 用户看不到其他进程的 PID。输出里能看到进程名和 PID,比如:
text复制LISTEN 0 128 0.0.0.0:9090 0.0.0.0:* users:(("java",pid=12345,fd=13))
然后想确认这个进程是不是以前的僵尸进程,可以 ps -p 12345 -o pid,ppid,cmd --no-headers。如果确认是残留进程,kill 12345 或者更温柔的 kill -TERM,等几秒再查。杀不掉再考虑 kill -9,但那是最后手段。更隐蔽的情况是 IPv6 和 IPv4 端口显示冲突,ss 里会看到 [::]:9090,那表示 IPv6 监听,IPv4 连接也能被它接收,这不一定冲突。判断标准是:你是想监听 IPv4 还是 IPv6,以及协议栈参数 net.ipv6.bindv6only 是否为 0。这个细节在容器环境里特别常见。
5.2 面试官问 Linux 命令,其实问的是场景
“Linux 面试题测试”里那些命令题,表面考命令,实际考的是你在真实环境里有没有解决过问题。比如面试官问“怎么找出日志文件里访问量最高的前十个 IP”,如果你只背过 awk 而不知道为什么要排序去重,就很容易卡住。正常的思路是:先用 awk 提取 IP 列,sort 排序,uniq -c 去重计数,再 sort -nr 按次数降序,最后 head -10:
bash复制awk '{print $1}' access.log | sort | uniq -c | sort -nr | head -10
这一串命令每个都有明确职责,面试官要听的不是命令本身,而是你面对“日志分析”这个场景时的拆解思路。再比如“如何排查系统负载升高”,不能只说 top。要结合 vmstat 看是不是 CPU 还是 IO 瓶颈,用 iostat 看磁盘等待,用 free 看内存是否不足,再用 ps 找出具体进程。Linux 运维面试不是背书测试,是把你扔到一台出了问题的服务器前,看你怎么用命令找答案。所以学习命令,要一个场景一个场景地练,而不是刷一遍命令大全。
5.3 多 GPU 三卡同时测试与 PCIe BAR 空间问题
热词里有“linux 三个gpu同时测试”,这在实际跑 AI 训练时很常见。最简单的测试方式是先用 nvidia-smi 看三张卡是否都被驱动正确识别,然后跑一个 GPU 负载工具。比如用 GPU Burn 或简单的 PyTorch 矩阵乘法,三张卡同时加载,观察温度、功耗和利用率。但这时经常会出现一个底层问题:系统日志里报“内核无法给 PCIe 桥接器分配足够的内存映射空间(BAR 地址)”,导致某块卡认不出来。
这个问题多发生在服务器开启 SR-IOV、或者直通配置复杂、BIOS 预留内存不足的时候。先别急着换硬件,可以尝试在内核启动参数里加上 pci=realloc,它允许内核重新分配 PCI BAR 空间。如果还是不行,检查 BIOS 里 Above 4G Decoding 选项,很多主板默认未开启,导致多 GPU 或大显存卡无法映射。这个坑在真实服务器上非常典型,命令行排查只能看到 dmesg | grep -i pci 报错,但真正解决往往要进 BIOS。能到这里来的人,已经不算是 Linux 新人了。
我把这一节放在最后,是想提醒大家:热词里的“Linux 常用命令大全”只是一个入口,真正的成长是在一个个具体故障和项目中完成的。
