Linux 系统服务
前言
- 这篇覆盖「在一台 Linux 上把活干完」的成套工具:压缩打包、管道与重定向、看网络看进程、管服务看日志、防火墙、SSH 远程连接、装软件。
- 环境同前两篇:Ubuntu 26.04.1 LTS(WSL2),systemd 259,日期 2026-11-03。为了把每条命令真跑一遍,我在这台 WSL 里额外装了
zip unzip bzip2 net-tools ufw openssh-server gcc make build-essential(apt装完都留着,磁盘从 1.3G 涨到 2.0G),测完把ufw、ssh都恢复成了未启用状态。
压缩与打包:先分清两件事
这是新手最容易混的两个概念:
| 概念 | 干什么 | 工具 |
|---|---|---|
| 打包(archive) | 把很多文件合成一个文件,顺便保留权限/属主/目录结构,不一定变小 | tar |
| 压缩(compress) | 把一个文件变短,不处理目录结构 | gzip、bzip2、xz、zstd |
所以 .tar.gz 其实是两步:先 tar 打包成 .tar,再 gzip 压缩。.zip 则是一步到位——zip 同时干了打包和压缩两件事,这也是它在跨平台时更省事的原因。
原笔记那句「gzip/bzip2 只能压缩单文件,不能压缩文件夹」是对的,但要补:它们遇到目录直接报错,而 tar 天生就是为目录设计的。
一个有意思的实测对比(三个 18 字节的小文件):
1 | $ ls -l 123.zip pkg.tar.gz | awk '{print $5, $9}' |
小文件多的时候 zip 反而更大,因为每个文件都有自己的目录项和压缩头,压不动;tar.gz 把所有内容连成一个流再压,效率高得多。文件多而小的时候别用 zip。
gzip / bzip2 / xz / zip 实测
gzip:默认吃掉源文件
1 | $ gzip 1.txt |
| 用法 | 作用 |
|---|---|
gzip 文件 |
压缩并删除源文件(实测) |
gzip -k |
保留源文件 |
gzip -d / gunzip |
解压,两者等价 |
gzip -1 ~ -9 |
压缩级别,实测 18 字节的文件压成 44 字节(比原文件还大,因为有小头部开销) |
zcat / gzcat |
不解压直接看内容 |
gzip -t |
只测试完整性 |
对已存在的目标,gzip 会问一句并跳过,实测:gzip: 2.txt already exists; not overwritten。
bzip2 / xz
1 | $ bzip2 1.txt |
xz 压得最小但最慢,gzip 最快、兼容性最好,bzip2 是两者之间的老折中。日常结论:分发源码包用 .tar.xz 或 .tar.gz,日志轮转用 gzip(logrotate 默认就是它)。
zip / unzip
1 | $ zip 123.zip 1.txt 2.txt 3.txt |
| 命令 | 作用 |
|---|---|
zip -q |
静默,实测真的没有输出 |
zip -r |
递归,打包目录必加 |
zip -d 包 文件 |
从包里删文件 |
zip -F / -FF |
修复损坏的包(-FF 更彻底) |
unzip -l |
只列不解压 |
unzip -o |
覆盖已存在文件不提示 |
unzip -d 目录 |
解到指定目录 |
unzip -q |
静默 |
zipinfo 包 |
第三种列法,格式和 ls -l 一样 |
版本实测:This is Zip 3.0 (July 5th 2008), by Info-ZIP——zip 这个格式二十多年没大改过,胜在到处都能开。
tar:最常用的打包工具
三个主选项互斥(一次只能选一个),加上修饰选项:
1 | $ tar --help | grep -E -- "-c, --create|-x, --extract|-t, --list|-C, --directory" |
| 选项 | 作用 | 实测 |
|---|---|---|
-c |
创建包 | tar -cvf all.tar 1.txt 2.txt 3.txt 输出三个文件名 |
-x |
解包 | — |
-t |
只列内容 | tar -tf all.tar 列出 1.txt 2.txt 3.txt |
-v |
显示过程 | -tvf 还能看到权限、属主、大小、时间 |
-f |
指定包文件名,必须紧挨着文件名 | — |
-z |
顺便用 gzip | 生成 all.tar.gz |
-j |
顺便用 bzip2 | 生成 .tar.bz2 |
-J |
顺便用 xz | 生成 all.tar.xz,实测 208 字节 |
-C 目录 |
切换工作目录(解到别处 / 从别处打包) | tar -xvf all.tar -C outdir 实测解出三个文件 |
--exclude |
排除 | tar --exclude='3.txt' -cvf ex.tar 1.txt 2.txt 3.txt 实测包里只剩 1、2 |
体积实测(同样三个小文件):
1 | $ ls -l all.tar all.tar.gz all.tar.xz | awk '{print $5, $9}' |
all.tar 是 10240 字节是因为 tar 默认按 20 字节的块对齐、一次写满一个 10 KB 的块,不是真实数据量——这也是为什么小文件打包后 tar 显得虚胖。这里 xz 反而比 gzip 大,同样是「内容太小、头部开销占主导」,文件大时 xz 通常明显更小。
三个高频用法,直接抄:
1 | tar -czvf 项目.tar.gz 项目目录/ # 打包 + gzip 压缩 |
两个实测到的细节:
- 新版 tar 会自己识别压缩格式:
tar -xvf all.tar.gz不加-z也能正常解压(实测解出三个文件)。所以解压时统一写tar -xvf就行,不用猜是 gz 还是 xz。 --exclude必须放在文件名之前,原笔记那条tar -zcvf 12.tar.gz *.txt --exclude实测报错:
1 | $ tar -cvf ex2.tar *.txt --exclude |
.tgz 是 .tar.gz 的简写,实测 tar -tzvf all.tgz 正常列出内容;.tar.bz2 的简写是 .tbz2/.tbz。
管道与 xargs
| 把前一个命令的标准输出接到后一个命令的标准输入。前提是后一个命令愿意读标准输入——这一点是所有管道困惑的根源。
1 | $ ls | grep "^[12]" |
反例,实测:
1 | $ echo 1.txt 2.txt | rm |
rm 不读标准输入,它只要参数,所以管道到这里就断了,两个文件一个都没删(实测当时目录里 7 个 txt 全在)。这就是 xargs 存在的意义:把标准输入变成命令行参数。
1 | $ echo "1.txt 2.txt" | xargs rm -f |
原笔记那条 find -name 2.txt | xargs rm -rf 思路对,写法有两个问题:find 不写路径默认从当前目录开始(见《Linux 文件操作》),以及删单个文件用 rm -f 就够,别习惯性 -rf。实测:
1 | $ find . -maxdepth 1 -name "2.txt" | xargs rm -f |
带空格的文件名会翻车,因为 xargs 默认按空白切分。实测两种写法对比:
1 | $ find . -maxdepth 1 -name "*.txt" | xargs echo 参数是: |
所以标准写法永远是 find ... -print0 | xargs -0 ...(或者干脆用 find ... -exec ... +,见上一篇)。
xargs 的其他常用项,实测:
| 写法 | 作用 | 实测输出 |
|---|---|---|
xargs -n1 |
每次只给一个参数 | printf 'a b c\n' | xargs -n1 echo → 三行,每行一个 |
xargs -I{} |
用占位符决定参数位置 | printf 'one two\n' | xargs -I{} echo 替换:{} → 替换:one two |
xargs -0 |
按 NUL 分隔(配 -print0) |
见上 |
xargs -d |
自定义分隔符,如 -d, 处理逗号列表 |
— |
xargs -P2 |
并行跑,2 个进程 | — |
参数太多时 xargs 会自动分批执行(实测把 2000 个参数喂给它照样成功),这正是它比 shell 通配符强的地方:rm *.txt 在文件极多时会报 Argument list too long,find | xargs rm 不会。
重定向与文件描述符
每个进程一出生就有三个打开的文件描述符,实测能直接看到:
1 | $ ls -l /proc/self/fd |
| 编号 | 名字 | 默认去哪 |
|---|---|---|
| 0 | stdin 标准输入 | 键盘(终端) |
| 1 | stdout 标准输出 | 终端 |
| 2 | stderr 标准错误 | 终端 |
1 和 2 是两个独立的流,这是重定向所有坑的来源。
1 | $ ls > o1.txt # 覆盖写:文件不存在就建,存在就清空 |
把错误也写进同一个文件有两种写法,实测都有效:
1 | $ ls /不存在xyz > both.txt 2>&1 # 先把 2 指向 1 的当前位置,再重定向 1 |
顺序错了结果完全不同,实测:
1 | $ ls /不存在xyz 2>&1 > order.txt |
因为 2>&1 先执行时,1 还指向终端,于是错误被绑到终端;之后 > order.txt 只改了 1。记法:> 文件 2>&1(先重定向 1,再让 2 跟上)才是「两个都进文件」。
其余常用招数,全部实测:
1 | $ command > /dev/null 2>&1 # 输出全丢,只保留退出码(实测退出码 2) |
原笔记里 ls /xxx /home &> 1.tt 那个 1.tt 是笔误。补三条实战常用:
exec > /tmp/run.log 2>&1之后脚本里所有输出都进日志。cmd 2>/dev/null只屏蔽错误(find /时必备)。cmd > file 2>&1与cmd &> file在 bash 里等价,但前者是 POSIX 标准写法,脚本里用前者。
网络服务与网卡
原笔记这一节的东西今天变化最大,逐条实测:
| 老命令 | 现状(实测) | 现代替代 |
|---|---|---|
ping |
还在,/usr/bin/ping |
一样用:ping -c 2 域名 |
ifconfig |
默认没装,属于 net-tools 包(我 apt 装上才有的) |
ip addr / ip -br addr |
netstat |
默认没装,同属 net-tools |
ss(/usr/bin/ss,默认可用) |
ifup / ifdown |
没装,属于 ifupdown 包,Ubuntu 已不用它 |
netplan(netplan.io 1.2-1ubuntu5 装着) |
server networking start |
这个命令不存在(command -v server 无输出) |
systemctl restart systemd-networkd |
ping 实测(WSL 里访问公网走的是 Windows 的网络栈):
1 | $ ping -c 2 archive.ubuntu.com |
-c 指定次数很关键:不加的话 ping 会一直发,新手常以为卡死了。
ip 的三条最常用的,实测输出:
1 | $ ip -br addr |
注意 192.168.104.119 这个地址:WSL2 默认是 NAT 网络,Linux 侧拿到的是 Hyper-V 虚拟网段的地址,Windows 从局域网访问它需要端口转发;这也是为什么「在 WSL 里起了服务,手机连不上」。要练真实服务器网络,还是得上云主机或虚拟机桥接。
DNS 配置在 WSL 里是自动生成的:
1 | $ head -3 /etc/resolv.conf |
普通 Ubuntu 服务器上,网络配置看 /etc/netplan/*.yaml(netplan.io 已装),但这台 WSL 里 /etc/netplan 是空的,网络由 WSL + systemd-networkd 负责。所以「改 netplan 配静态 IP」这类操作在 WSL 里不适用。
看端口、看资源、看进程
netstat 与 ss
netstat -tunpl 是原笔记的写法,实测(-t TCP、-u UDP、-n 不解析名字、-p 显示进程、-l 只看监听):
1 | $ netstat -tunpl | head -6 |
同一件事用 ss 更快、信息更准:
1 | $ ss -tunlp | head -4 |
上面那行 0.0.0.0:22 LISTEN 1/init 很有教学意义:那时我还没启动 sshd,但 22 端口已经被 PID 1(systemd)占着——因为新版 Ubuntu 的 ssh 用 socket 激活,ssh.socket 先占住端口,有连接进来才拉起 ssh.service。ss 的输出里能同时看到两个持有者:
1 | $ ss -lntp | grep :22 |
lsof 也能干这件事,实测用一个临时监听验证:
1 | $ nc -l -p 8899 & # 起一个监听 |
看总量用 ss -s:
1 | $ ss -s |
排查「端口被占用」的三件套:ss -lntp | grep 端口 → lsof -i :端口 → ps -p PID -o pid,user,cmd。
free / w / uptime / top
1 | $ free -h |
free 一定要看 available 而不是 free:buff/cache 里那 581 Mi 是页缓存,内存紧张时内核会自动让出来,所以真正能用的是 7.2 Gi。-b/-k/-m/-g 实测分别是 8329555968 / 8134332 / 7943 / 7,单位是字节/KiB/MiB/GiB。
w 一行就把「几个人在线、各自在干什么、负载多少」说完了,比 who + ps 方便。load average 三个数是 1/5/15 分钟平均,和 CPU 核数比才有意义(本机 8 核,2.20 属于轻载)。
top 交互界面在无终端环境下没法测,但批处理模式可以,实测 top -b -n 1 的前四行正是原笔记说的结构:
1 | top - 18:21:17 up 9 min, 2 users, load average: 0.88, 0.60, 0.23 |
top 里几个必学的交互键:P 按 CPU 排序、M 按内存、c 显示完整命令行、1 展开每个核、k 杀进程、q 退出。htop 更好用但默认没装(实测)。
ps 的两种写法与实用查询
1 | $ ps -ef |
-ef 和 aux 结果几乎一样,差别只在列:-ef 有 PPID(父进程),aux 有 %CPU/%MEM/VSZ/RSS/STAT。找父子关系用 -ef,看资源占用用 aux。
比 ps -ef | grep xxx 更好的写法,实测:
1 | $ ps -C bash -o pid,user,etime,cmd |
pgrep/pidof 直接给 PID,ps -ejH 给进程树,实测:
1 | $ pgrep -c bash |
进程管理:kill 与信号
kill 不是「杀」,是「发信号」。实测起一个后台 sleep 再操作它:
1 | $ sleep 300 & |
| 信号 | 编号 | 行为 | 实测 |
|---|---|---|---|
TERM |
15 | 默认信号,请求进程自己收尾(可被捕获/忽略) | kill 2062 后进程消失 |
KILL |
9 | 内核直接回收,进程无法捕获,没有清理机会 | kill -9 2068 后 ps -p 查不到 |
HUP |
1 | 挂断;对守护进程的习惯用法是「重载配置」 | — |
INT |
2 | 相当于 Ctrl-C |
— |
STOP |
19 | 暂停(不可捕获) | 实测 STAT 变成 T+ |
CONT |
18 | 继续 | 实测 STAT 回到 S+ |
QUIT |
3 | 退出并产生 core dump | — |
STOP/CONT 这组实测最有意思:
1 | $ kill -STOP 2075; ps -p 2075 -o stat= |
先 STOP 再 CONT 是「冻结一个进程」的标准手法,比直接 -9 温和得多(比如临时按住一个失控的程序)。
pkill 按名字/命令行杀,实测:
1 | $ sleep 200 & |
-f 表示匹配完整命令行(不加 -f 只匹配进程名,15 字符以上还会截断)。用完记得 pkill -f 有可能误伤自己,写模式时先用 pgrep -f 看一眼。
STAT 列的字母含义(实测本机统计):
1 | $ ps -eo stat= | cut -c1 | sort | uniq -c |
R 运行/就绪、S 可中断睡眠、D 不可中断睡眠(等 IO,常见于磁盘卡住)、T 停止、Z 僵尸(子进程退出但父进程没回收)。Z 进程 kill -9 也杀不掉,只能处理它的父进程。
nice 调优先级,实测可用(值越大越谦让,范围 -20 到 19):
1 | $ nice -n 10 sleep 1 & |
vmstat 装了,iostat/sar/dstat/htop 都没装(属于 sysstat、htop 包,要用得 apt install)。
systemd:管服务的正确姿势
原笔记用 service ssh start|stop|restart 控制服务。实测在 Ubuntu 26.04 上,service 只是个包装脚本(/usr/sbin/service 开头注释写着「A convenient wrapper for the /etc/init.d init scripts」),它把命令转给 systemctl,所以两种写法都能用,但该学的是 systemctl。
以 cron 为例,五个必学命令,全部实测:
1 | $ systemctl status cron --no-pager | head -6 |
status 那行里的 enabled / active (running) 是两个不同维度:enabled = 开机自启,active = 现在在跑。四种组合都合法,最常见的困惑是「服务在跑但重启后没了」= active + disabled。
启停与开关机自启,实测:
1 | $ systemctl stop cron && systemctl is-active cron |
三个要点:
- 不是所有服务都支持
reload(单元文件里得有ExecReload)。cron 不支持,所以要用reload-or-restart。 enable只是建软链接(把单元挂进/etc/systemd/system/multi-user.target.wants/),不会立刻启动;想「启用并马上启动」用systemctl enable --now 服务。disable那两行输出说明白了一件事:systemd 会同步 SysV 脚本(/etc/init.d/cron确实还在,3103 字节,#!/bin/sh开头)。这就是《Linux 入门》里说「/etc/rc5.d还只是兼容层」的同一个机制。
自己改了单元文件(/etc/systemd/system/*.service)之后必须:
1 | $ systemctl daemon-reload |
没做这一步的话,systemctl 会提醒你,实测看到过这条警告:Warning: The unit file, source configuration file or drop-ins of cron.service changed on disk. Run 'systemctl daemon-reload' to reload units.
日志:journalctl 与 /var/log
systemd 的日志进 journald,传统服务的日志还是写文件,两套并存,实测本机都有:
1 | $ journalctl --no-pager -u cron -n 3 |
journalctl 的常用过滤,实测都可用:
| 命令 | 作用 |
|---|---|
journalctl -u 服务名 |
只看这个服务(排障第一招) |
journalctl -u 服务 -f |
实时跟踪 |
journalctl -b / -b -1 |
本次 / 上次开机的日志 |
journalctl --since "10 min ago" / --since -10min |
按时间 |
journalctl -p err |
只看 error 及以上级别 |
journalctl --disk-usage |
占了多少磁盘(实测 48M) |
journalctl --vacuum-size=100M |
清理到指定大小 |
journalctl -xe |
最后 20 行 + 崩溃现场,带 --no-pager 更好用 |
-p err -b 实测直接暴露了这台机器上两个真实故障(ubuntu-insights-collect.service: Failed to set up mount namespacing、WSL 显卡驱动的 Ioctl failed: -2)——这就是它的价值。
内核消息还能从 dmesg 看(dmesg | tail),它和 journal 是同一份数据的两个入口。
rsyslog.service 在跑(实测 list-units 里有),所以 /var/log/syslog、/var/log/auth.log 这些传统文件仍然有;它们的权限是 -rw-r----- syslog adm,普通用户读不了,要 sudo 或把自己加进 adm 组。轮转由 /etc/cron.daily/logrotate 和 logrotate.timer 负责(实测两个都在)。
定时任务:cron 与 systemd timer
crontab 的字段顺序是分 时 日 月 周 命令,实测写入再读回:
1 | $ crontab -l |
| 位置 | 字段 | 范围 | 例子 |
|---|---|---|---|
| 1 | 分 | 0-59 | 5 0 * * * = 每天 00:05 |
| 2 | 时 | 0-23 | 0 3 * * * = 每天 03:00 |
| 3 | 日 | 1-31 | */10 * * * * = 每 10 天 |
| 4 | 月 | 1-12 | — |
| 5 | 周 | 0-7(0 和 7 都是周日) | 0 9 * * 1-5 = 工作日 9 点 |
| 6 | 命令 | — | 绝对路径,或自己 cd |
系统级的 /etc/crontab 多一个用户名字段(实测文件头注释就写着「These files also have username fields, that none of the other crontabs do」),SHELL=/bin/sh 也在那里定义。
/etc 下和定时有关的目录,实测内容:
1 | $ ls /etc/cron.d |
/etc/cron.hourly、cron.weekly、cron.monthly 也是同一族,往里放一个可执行脚本就会被定时执行。
新系统更推荐 systemd timer(本机实测在跑的一堆):
1 | $ systemctl list-timers | head -6 |
timer 比 cron 好在:能看下一次/上一次执行时间、日志进 journal、支持依赖和随机延迟(RandomizedDelaySec)。日常结论:给用户用的定时任务写 crontab,给系统组件写的用 timer,别混。
WSL 的一个坑:wsl --shutdown 之后 cron 里错过的任务不会补跑(VM 停了就是停了),所以别把重要定时任务放在 WSL 里。
防火墙:ufw 全流程实测
ufw = Uncomplicated Firewall,默认没装(实测 command -v ufw 为空),装它:sudo apt install ufw(版本 0.36.2-9build1)。下面是在我这台机器上完整跑过一遍的流程,最后已经关掉恢复原状。
1 | $ sudo ufw status |
必须记住的几条:
ufw enable之前一定要先ufw allow OpenSSH(或 22/tcp),否则远程连接立刻断掉,只能回到机房/控制台救。这是新手最常见的自断后路操作。- 默认策略
deny (incoming), allow (outgoing):进来的全拒、出去的全放,这正是服务器要的。 - 每条规则会自动生成 v4 和 v6 两份,所以
status numbered里编号会跳。 ufw delete allow 5900按完整规则文本删;按编号删(sudo ufw delete 1)会交互确认,脚本里别用。ufw limit是防暴力破解用的(限制连接频率)。- 原笔记那句
sudo ufw alllow 5900是拼写错误。
「底层依赖 iptables」这句实测成立——iptables -L -n 里能看到 ufw 建的一整串链:
1 | $ iptables -L -n | head -6 |
规则本身存在 /etc/ufw/(实测有 before.rules、after.rules、user.rules、sysctl.conf、ufw.conf、applications.d)。iptables 1.8.11-2ubuntu3 是随 ufw 一起装上的。
云上还有一层安全组在实例外面,ufw 放行但安全组没放行照样连不上——排查端口问题时两层都要看。
远程连接:SSH
「Ubuntu 默认没有 ssh 服务」这句今天仍然成立,实测:sshd 未安装时 systemctl status ssh 报 Unit ssh.service could not be found。装它:
1 | $ sudo apt install openssh-server # OpenSSH_10.2p1 Ubuntu-2ubuntu3.8, OpenSSL 3.5.5 |
注意 ssh.socket 和 sshd@.service:新版 Ubuntu 默认用 socket 激活,systemctl start ssh 之后 status 里会出现 TriggeredBy: ● ssh.socket。这有两个实际后果:
1 | $ systemctl is-enabled ssh ssh.socket |
想彻底关掉 ssh,必须连 socket 一起停,否则 22 端口还被 systemd 占着(我实测踩过,ss -lntp | grep :22 仍显示 systemd,pid=1):
1 | $ sudo systemctl stop ssh ssh.socket |
服务端配置在 /etc/ssh/sshd_config,实测关键行都默认注释着:
1 | $ grep -nE "^#?(PermitRootLogin|PasswordAuthentication|Port|PubkeyAuthentication)" /etc/ssh/sshd_config |
所以原笔记那条「设置 root 远程连接」的做法(把 #PermitRootLogin prohibit-password 改成 PermitRootLogin yes)语法上没问题,但 yes 是危险选项:它允许 root 用密码登录,等于把最高权限账户暴露在爆破之下。生产环境的正确配置是:
1 | PermitRootLogin prohibit-password # 只允许密钥登录 root |
改 sshd 配置前,务必先跑一次语法检查(实测这条能救命):
1 | $ sudo sshd -t; echo $? |
sshd -t 只检查配置不启动服务,systemd 单元里也把它作为 ExecStartPre——配置写错服务根本起不来。改完重载:
1 | $ sudo systemctl restart ssh # 老写法 service ssh restart 实测等价 |
不要断开当前会话去验证新配置,另开一个终端测,连不上还能退回旧会话。
服务端起来之后,客户端连接实测:
1 | $ ssh 用户@主机 |
第二行虽然被拒绝,但 Permission denied (publickey,password) 恰恰证明 TCP 连接、版本协商、认证方式都通了——排查 ssh 问题时,「Permission denied」是服务端活着,「Connection refused / timed out」才是网络或服务的问题。
不登录也能验证服务在不在,直接读横幅:
1 | $ timeout 4 bash -c 'exec 3<>/dev/tcp/127.0.0.1/22; head -1 <&3' |
配套命令本机都有:scp、sftp、ssh-keygen。传文件优先 rsync(实测在,/usr/bin/rsync),它断了能续传、只传差异:
1 | rsync -avz --progress 本地目录/ 用户@主机:远端目录/ |
密钥登录的正确流程(比密码安全一个量级):
1 | ssh-keygen -t ed25519 -C "备注" # 本机生成密钥对 |
原笔记提到 putty/shell 工具——Windows 10 1809 起系统就自带 OpenSSH 客户端(PowerShell 里直接敲 ssh),Windows 11 自然也有,不再需要 PuTTY;想要图形界面可以用 Windows Terminal 或 VS Code 的 Remote-SSH。
装软件:apt、dpkg、源码三件套
apt:优先用它
apt 和 apt-get 是两个不同的可执行文件(实测大小 23072 与 59944 字节),命令基本一致;apt 的输出对人友好,apt-get 更稳定,脚本里用 apt-get(apt 会警告「does not have a stable CLI interface」)。
| 命令 | 作用 | 实测 |
|---|---|---|
apt update |
只刷新索引,不装东西 | 必须最先跑 |
apt install 包 |
安装(自动解决依赖) | 装 hello 时自动带上 libc6 |
apt remove 包 |
卸载但留配置 | 之后 dpkg -l 显示 rc 状态 |
apt purge 包 |
卸载并删配置 | 之后 dpkg -l 查不到 |
apt upgrade |
升级已装的包 | 实测模拟显示「131 not upgraded」 |
apt -s install 包 |
模拟,不真动手 | 排障/预演神器 |
apt list --installed |
列出已装(实测 581 行) | dpkg -l | grep -c '^ii' = 580 |
apt search 词 / apt show 包 |
找包 / 看详情 | — |
apt download 包 |
只下 .deb 不装 |
实测下到 26068 字节 |
apt clean |
清下载缓存 | 实测 /var/cache/apt/archives 从 54M 变 8K |
apt autoremove |
删掉不再需要的依赖 | 我用 -s 模拟,没真删 |
apt -f install |
修复断掉的依赖 | dpkg 装不上时的解药 |
apt-cache policy 包 |
看候选版本与来源 | 见下 |
软件源在 Ubuntu 24.04 之后换成了 deb822 格式,实测本机 /etc/apt/sources.list.d/ubuntu.sources:
1 | Types: deb |
不再是老教程里 sources.list 那种一行一条的写法,改源时要按这个格式改(国内加速常换成 USTC/清华镜像)。
apt-cache policy 是判断「为什么装不上/装的哪个版本」的第一命令,实测:
1 | $ apt-cache policy hello |
dpkg:只管本地 .deb
| 命令 | 作用 | 实测 |
|---|---|---|
dpkg -i 包.deb |
安装本地包,不管依赖 | 装 hello 成功,退出码 0 |
dpkg -r 包名 |
卸载(保留配置) | 之后 dpkg -l 包名 报 no packages found |
dpkg --purge 包名 |
卸载并删配置 | — |
dpkg -l |
列出包(grep -c '^ii' = 580) |
— |
dpkg -L 包名 |
这个包装了哪些文件 | /usr/bin/hello、/usr/share/doc/hello/ |
dpkg -S 文件路径 |
反查文件属于哪个包 | dpkg -S /usr/bin/hello → hello |
dpkg-deb -c 包.deb |
不安装也能看包内容 | 列出 ./usr/bin/hello 及权限 |
dpkg-deb -I 包.deb |
看包的元数据(依赖、描述) | Depends: libc6 (>= 2.38) |
两个实测到的坑:
dpkg -S /bin/ls报no path found matching pattern /bin/ls——因为 dpkg 数据库里记的是/usr/bin/ls(/bin只是软链接,见《Linux 目录结构及 VIM》)。反查时要用真实路径。dpkg -i遇到缺依赖会失败并且不会自动补,此时跑sudo apt -f install让它把依赖装上再继续。
WPS 那个例子(dpkg -i wps-office_*.deb 再手动放字体)思路仍然成立,只是字体目录建议放 /usr/share/fonts/truetype/ 后执行 fc-cache -f。
源码安装:configure → make → make install
原笔记的三步流程正确,但少了一个致命前置。实测:
1 | $ gcc /tmp/t.c -o /tmp/t |
只装 gcc 是编不了东西的——标准头文件属于 libc6-dev,最小化系统里没有。当时 ./configure 的报错也因此是:
1 | configure: error: C compiler cannot create executables |
正确做法是一条命令装齐:
1 | $ sudo apt install build-essential # 12.12ubuntu2.26.04.2,带上 gcc/g++/make/libc6-dev |
装完之后完整跑通了原笔记的三步(用 GNU hello 2.12.1 源码包,1033297 字节):
1 | $ tar -zxf hello-2.12.1.tar.gz |
make 的增量特性也实测到了:
1 | $ make |
以及 Makefile 的语法要求(实测 cat -A 确认):每条命令前必须是制表符 ^I,不是空格,否则 make 报 missing separator。
源码安装的最后一步很重要:它没有任何包管理器记录,dpkg -l 查不到、apt upgrade 不会更新它、卸载只能 rm -rf /tmp/mk/pfx(实测)。这就是为什么能用 apt 就别用源码装。
原笔记的 nginx 例子(--prefix=/usr/local/nginx、./nginx 启动、看 html 目录)流程正确,但我没在本机复现(下载 nginx 源码 + 装 pcre/zlib 开发库成本较高),且现代做法是用 systemctl 管 nginx 而不是直接跑 ./nginx。另外它写的依赖 libpcre3-dev 在新版 Ubuntu 上已改名,实测源里是 libpcre2-dev。
命令串联的三条,实测行为如原笔记所述:
1 | cmd1; cmd2 # 无条件依次执行,cmd1 失败也继续 |
常见坑速查
- 用
zip -l列内容:那是换行符转换选项,列内容用unzip -l或zipinfo(实测报Nothing to do!)。 - 以为
gzip 目录能压文件夹:单文件压缩工具遇到目录就报错,先tar再压缩。 tar的-C写成-c:-c是创建,-C才是切换目录(实测--help里两行分开)。--exclude放在文件名后面:必须放前面,实测报requires an argument。-f后面忘了紧跟文件名:tar -cvf *.tar.gz dir会把第一个匹配到的文件当成包名。- 管道给
rm/cp/mv:这些命令不读标准输入,要用xargs(实测echo a | rm报missing operand)。 find | xargs rm不带-print0/-0:文件名里有空格会被拆成两个参数(实测)。2>&1 > file:顺序错了,错误还是去了屏幕(实测),要写> file 2>&1。> file当追加用:>会清空原文件(实测 OLD 直接没了),追加用>>。ping不加-c:会一直发,看起来像卡死。- 想用
ifconfig/netstat:新系统默认没有net-tools,改用ip和ss。 - 照抄
server networking restart:这命令不存在,用systemctl。 free只看free列:要看available,buff/cache是可回收的。kill -9当默认手段:先kill(TERM)给它收尾机会,-9会让程序来不及清理。- 改了 systemd 单元不
daemon-reload:会收到「changed on disk, run systemctl daemon-reload」警告(实测)。 - 以为
enabled就等于在跑:enabled是开机自启,active才是当前运行。 ufw enable前没放行 SSH:远程连接立刻断,只能回控制台(务必先ufw allow OpenSSH)。- 只
systemctl stop ssh:ssh.socket还占着 22 端口,要stop ssh ssh.socket(实测)。 - 把
PermitRootLogin yes当常规配置:等于开放 root 密码爆破面,用prohibit-password+ 密钥。 - 改完 sshd 配置直接重启并断开当前会话:正确顺序是先
sshd -t检查、另开终端验证、再断开。 - 只装
gcc就编译:缺libc6-dev会报stdio.h: No such file or directory(实测),装build-essential。 - 源码装完忘了装在哪:没有包管理器记录,务必用
--prefix指定独立目录方便删除。 apt-get autoremove手快:它会删依赖,先用apt-get -s autoremove模拟看清单。
练习
- 造 200 个小文件,分别用
zip -r、tar -cf、tar -czf、tar -cJf打包,记录四者体积;再用tar -tzf和unzip -l各列一次内容。 - 写一条命令完成:把
/var/log下所有.log文件的路径列表喂给wc -l,分别用管道、xargs、find -exec ... +三种写法实现,并解释哪种在文件极多时最稳。 - 用重定向做一个「运行日志」:执行一段脚本,让它的正常输出和错误都进
run.log,同时屏幕上还能看到(提示:tee与2>&1的组合)。 - 找出这台机器上所有处于
LISTEN的 TCP 端口和对应进程,用ss和lsof各做一次,比较输出差别;再解释为什么netstat显示0.0.0.0:22的进程是 PID 1。 - 给
cron服务做一次完整的生命周期演练:status→stop→is-active→start→restart→reload(观察报错并解释原因)→disable→enable→daemon-reload,把每一步的输出记下来。 - 用
journalctl找出本机最近一次失败启动的服务及其错误日志(提示:-p err -b),并说明你会去哪三个地方继续查。 - 写一条 crontab:每天 02:30 执行
/usr/bin/true,用crontab -l验证,最后crontab -r清掉。再回答:/etc/crontab和用户 crontab 的字段差别是什么? - 在 WSL 上装
openssh-server,用sshd -t检查配置,systemctl start ssh后用ss -lntp和读横幅两种方式确认服务可用,然后用ssh-keygen+ssh-copy-id配好免密登录自己,最后把 ssh 停掉并 disable(含ssh.socket)。 - 开一个 ufw:先
ufw allow OpenSSH,再ufw enable,加三条规则(端口、from ... to any port ...、limit),用status numbered检查,删掉一条,最后ufw disable。全程记录输出。 - 只装
gcc编一个hello.c,观察报错;再用dpkg -S查出缺的是哪个包,apt install build-essential补齐后跑通configure --prefix→make→make install,最后用rm -rf卸载并说明源码安装为什么必须自己记住安装位置。
参考
- GNU tar 手册(选项、
-C、--exclude、自动识别压缩格式) - Info-ZIP 的 zip / unzip 手册页
- gzip 官方文档(含
-k、-d、zcat) - tukaani 的 XZ 工具文档(xz 压缩)
- GNU Bash 手册:Redirections 与 Here Document(
> file 2>&1的顺序语义) - GNU Bash 手册:Pipelines 与 xargs 的配合方式
- Linux 手册页:
xargs(1)(-0、-n、-I、-P) - iproute2 文档(
ip、ss的官方手册) - systemd 官方:
systemctl(1)与systemd.unit(5) - systemd 官方:
systemd.timer(5)与systemd.time(7) - systemd 官方:
journalctl(1)与日志级别 - Ubuntu 社区 wiki:UFW 防火墙用法
- Canonical:OpenSSH 服务端与客户端部署指南
- OpenSSH 官方:
sshd_config(5)(PermitRootLogin各取值) - Debian 参考手册:apt 与 dpkg 的分工
- GNU Autoconf:
configure与--prefix的作用 - Microsoft:WSL 网络架构与端口访问