Linux 干货速查
面向 Ubuntu 开发主机 + BusyBox 嵌入式开发板的个人速查。
约定
- ⚠️ 标记危险命令(误用会丢数据 / 断网 / 毁分区),执行前务必确认设备名和路径。
- 命令行为在不同实现下可能不同,差异处标注:(GNU)Ubuntu 自带 / (BusyBox)板子 / (systemd)。
- 拿不准的地方标注「待确认」,不猜。
目录
- Shell 与环境变量
- 文件与目录操作
- 文本搜索与处理
- 磁盘、分区与文件系统
- 内存、CPU 与 I/O 排查
- 网络配置与抓包
- 文件传输与共享
- systemd 与系统服务
- 设备管理:udev 与 uevent
- 交叉编译工具链
- 常见故障与排查案例
Shell 与环境变量
重定向
从文件读入 stdin:
grep "search_term" < file.txt # 等价 grep "search_term" file.txt
注:原写法
cat < file.txt | grep ...里的cat是多余的,直接grep ... file.txt即可(避免 useless use of cat)。
| 符号 | 作用 |
|---|---|
> / >> | 覆盖 / 追加 stdout |
2> | 重定向 stderr |
2>&1 | stderr 合并到 stdout |
&> file | stdout+stderr 一起(bash 专有;BusyBox ash 不支持,用 > file 2>&1) |
< file | 从文件读 stdin |
持久化环境变量
写进 ~/.bashrc(当前用户)或 /etc/profile(全局),常用于把工具链、代理等加进 export PATH / export XXX。工具链一键配置见 交叉编译工具链。
文件与目录操作
分类:复制/移动/删除 · 链接 · 时间戳 · 归档压缩 · 查找
cp
复制文件/目录。
cp -a src/ dst/ # 归档式复制:保留权限/属主/软链接/时间戳(= -dpR)
-a:等于-d -p -R的集合-d:源为软链接时,目标也建为软链接(对硬链接无效)-r/-R:递归复制目录-l:把目标建为源文件的硬链接,而非复制-s:把目标建为源文件的软链接,而非复制
ln
创建链接。
ln -s mke2fs mkfs.ext2 # 软链接:ln -s [目标] [链接名]
-s:创建软链接(不加-s是硬链接)- 软链接特点:可跨分区/文件系统、可指向目录;源被删则变成"死链接"(
ls里常显红色)
touch
修改时间戳 / 创建空文件。
touch file # 文件不存在则创建空文件;存在则更新 atime/mtime
touch -d "2026-01-01" file
权限:root 可保留复制的属主/属组,普通用户不行。
rm / 回收站
⚠️ rm -rf 不可恢复,删前确认路径;尤其变量为空时 rm -rf "$DIR"/ 可能变成 rm -rf /。
日常建议用 trash-cli(开发主机)代替 rm:
trash-put /path/to/file # 删除到回收站
trash-list # 查看回收站
trash-restore # 恢复
trash-empty # 清空
trash-empty <days> # 清空 N 天前的
归档与压缩
tar
tar -zcvf out.tar.gz dir/ # 打包 + gzip
tar -zxvf out.tar.gz # 解压 gzip
tar -Jxvf out.tar.xz # 解压 xz
tar -ztvf out.tar.gz # 只看内容不解压
-c建立归档 /-x解压 /-t看内容 /-r追加 /-u更新-f指定档案名,必须是最后一个参数,后面紧跟文件名-zgzip /-jbz2 /-Jxz /-Zcompress(.Z)-v显示过程 /-O解到标准输出
(BusyBox)tar 是子集,部分构建不带
-J/-j/-Z,需外部xz/gzip配合。
unzip
unzip file.zip # 解压到当前目录
unzip file.zip -d /target/ # 解压到指定目录
-d 目录指定输出目录 /-l只列内容 /-o覆盖不询问 /-n不覆盖 /-q安静 /-j不建目录
7za
7za x archive.7z -r -o./ # 按原目录树解压到当前目录
x按原始目录树解压(e会把所有文件解到同一层)-r递归子目录 /-o指定输出目录,-o后无空格直接跟路径
find
find [路径] [条件] [动作]
# 删除当前目录下除 system 外的所有第一层项(无交互)⚠️
find . -mindepth 1 -maxdepth 1 ! -name system -exec rm -rf {} +
-mindepth 1:排除.自身-maxdepth 1:只匹配第一层,不递归! -name system:排除名为system的项-exec rm -rf {} +:批量删除匹配项
环境差异:
{} +(批量传参)是(GNU)find;(BusyBox)find 的-exec多数构建只支持以\;结尾,即-exec rm -rf {} \;(逐个执行,较慢)。
ls
列目录。
-a含隐藏文件 /-l详细 /-h人类可读大小(配合-l)-t按修改时间 /-S按大小 /-r反序-f不排序直接输出(大目录更快,隐含-a)
文本搜索与处理
grep
文本搜索。
grep -n "pattern" file # 带行号
grep -rn "pattern" . # 递归搜索目录
grep -o "pattern" file # 只输出匹配的部分
grep "pattern" f1 f2 f3 # 多文件
# 仅(GNU)grep 支持 include/exclude:
grep -rn "main()" . --include=*.{c,h,cpp}
grep -rn "main()" . --exclude="README" --exclude-from=filelist
-n行号 /-r、-R递归 /-o只出匹配部分 /-i忽略大小写 /-v反选 /-E扩展正则
环境差异:(BusyBox)grep 支持
-n/-r/-o/-i/-v/-E;不支持--include/--exclude/--exclude-from。
rg(ripgrep)
比 grep 快,默认递归、默认遵守 .gitignore。开发主机用,板子一般没有。
rg "pattern" # 当前目录递归
rg -n "pattern" -g "*.c" # 限定文件类型
rg -i "pattern" # 忽略大小写
sort
对文本按行排序。
sort file.txt # 升序
sort -u file.txt # 排序并去重
sort -rn file.txt # 按数值倒序
sort -t: -k2 file.txt # 以 ":" 分隔,按第 2 列排
-u去重 /-r倒序 /-n按数值 /-k N按第 N 列 /-t C指定分隔符
示例:
$ cat sort.txt
aaa:10:1.1
ccc:30:3.3
bbb:20:2.2
eee:50:5.5
eee:50:5.5
$ sort -u sort.txt # 排序 + 去重
aaa:10:1.1
bbb:20:2.2
ccc:30:3.3
eee:50:5.5
scp
hexdump
十六进制查看二进制。
hexdump -C file.bin # 十六进制 + ASCII
hexdump -C -s 0x100 -n 64 file # 从偏移 0x100 起看 64 字节
-C规范的十六进制+ASCII /-n length处理长度 /-s offset跳过开头字节
cloc
统计代码行数(开发主机)。
cloc --include-lang="C,C++" . --exclude-dir="Reference_project,3rdpart,SDK"
--include-lang只统计指定语言 /--exclude-dir排除目录
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
C++ 62 2557 1224 11747
C 1 134 61 633
-------------------------------------------------------------------------------
SUM: 63 2691 1285 12380
-------------------------------------------------------------------------------
- code:真正代码行(不含空行/注释) / comment:注释行 / blank:空行 / files:文件数
磁盘、分区与文件系统
df
看文件系统占用(排查"磁盘满"第一步)。
df -h # 各文件系统占用,人类可读
df -h / # 只看根
df -i # 看 inode 占用(文件数满同样导致写失败)
du
看目录/文件占用。
du -h -d1 # 当前目录下一层各项大小
du -sh dir/ # 目录总大小
-h人类可读 /-s只出总计 /-d N限制深度
环境差异:
--max-depth=1是(GNU)写法;-d1在 GNU 和 BusyBox 均可用,更通用。
mount
挂载文件系统。
mount -o remount,rw / # 只读 rootfs 重挂为可写(板子常用)
mount -t tmpfs -o size=64M tmpfs /mnt/ram # 挂内存文件系统
mount -t nfs 192.168.2.100:/home/meian/nfs_share /mnt # 挂 NFS
-t指定文件系统类型 /-o选项tmpfs:基于内存的临时文件系统,占用随写入增长,上限由size限定
mkfs / mke2fs
⚠️ 格式化会清空整个分区,认准设备名,写错分区会毁掉系统盘。
sudo mkfs.ext4 /dev/sdX1 # 格式化为 ext4
mke2fs -t ext4 /dev/sdX1 # 等价;mkfs.extN 本质是指向 mke2fs 的软链接
dd
⚠️ 按扇区裸写,of= 写错设备直接覆盖目标盘、不可恢复。 先 lsblk 确认设备名,注意 /dev/sdX 是整盘、/dev/sdX1 是分区。
sudo dd if=image.img of=/dev/sdX bs=4M status=progress conv=fsync # 烧写镜像
sudo dd if=/dev/mmcblk0p1 of=part1.img bs=4M # 备份分区为镜像
环境差异:
status=progress是(GNU)dd;(BusyBox)dd 无此选项。
内存、CPU 与 I/O 排查
free
看内存占用。输出格式因版本差异较大:
新版(GNU / procps-ng,Ubuntu):关注 available(真正可立即分配的量,已扣除可回收缓存),比 free 列更有参考意义。
total used free shared buff/cache available
Mem: 16384000 ... ... ... ... ...
Swap: ...
buff/cache = Buffers + Cached + SReclaimable
旧版(BusyBox / 老 procps,多见于板子):带 -/+ buffers 行。
total used free shared buffers
Mem: 250444 95988 154456 8 1968
-/+ buffers: 94020 156424
Swap: 0 0 0
- Mem 行:
total总量 /used已分配(含内核缓存)/free完全空闲 /shared共享内存(tmpfs、shmget)/buffers块 I/O 缓冲 - -/+ buffers 行:把 buffers 剔除后的视角——
used = Mem.used - buffers(应用真正占用)、free = Mem.free + buffers(真正可立即分配) - Swap 行:全 0 表示未配置 swap
iostat
看 CPU 与块设备 I/O(来自 sysstat 包,BusyBox 默认无)。
~ # iostat
Linux 4.9.138 (Smart Home) 07/28/25 _armv7l_ (2 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
5.45 0.00 11.98 0.00 0.00 82.57
Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
mtdblock3 0.00 0.07 0.00 9058 0
sda 0.69 0.01 4.12 1530 540352
- avg-cpu:
%user用户态 /%system内核态 /%iowait等 I/O 空闲 /%idle空闲 /%nice低优先级用户态 /%steal虚拟化被抢占 - Device:
tps每秒 I/O 次数 /Blk_read/s、Blk_wrtn/s每秒读写块数(默认块 512B)/Blk_read、Blk_wrtn开机以来累计
排查经验:
%iowait高 → 硬盘 I/O 瓶颈;%idle高但响应慢 → 可能在等内存,考虑加内存;%idle持续 <10 → CPU 处理能力不足。
网络配置与抓包
⚠️ 改网络配置可能断掉当前 SSH/telnet 连接,远程操作前想好恢复手段。
ifconfig / ip(配置 IP、网关、MAC)
# 设置 IP / 掩码
ifconfig eth0 192.168.2.206 netmask 255.255.255.0 # net-tools / BusyBox
ip addr add 192.168.2.206/24 dev eth0 && ip link set eth0 up # iproute2(Ubuntu 推荐)
# 默认网关
route add default gw 192.168.2.1 # net-tools / BusyBox
ip route add default via 192.168.2.1 # iproute2
# 改 MAC(先 down 再改再 up)
ifconfig eth0 down
ifconfig eth0 hw ether 00:11:22:33:44:55
ifconfig eth0 up
# iproute2 等价:
ip link set eth0 down
ip link set eth0 address 00:11:22:33:44:55
ip link set eth0 up
环境差异:(BusyBox)板子一般只有
ifconfig/route;(GNU)Ubuntu 上ifconfig需装net-tools,官方推荐ip。
udhcpc
(BusyBox)DHCP 客户端,向 DHCP 服务器申请 IP/掩码/网关/DNS。
udhcpc -i eth0 -b -p /tmp/udhcpc.eth0.pid
-i <iface>:指定网卡-b:后台运行(未立刻拿到 IP 时转后台重试,避免同步调用卡死)-p <file>:写 PID 到文件,便于按网卡精确 kill,避免误杀-f:前台运行(默认调试用)-q:拿到租约后立即退出(单次获取)
tcpdump
抓包(BusyBox 默认多无,需交叉编译;开发主机 sudo apt install tcpdump)。
tcpdump -i eth0 -nn # 抓 eth0,不解析主机名/端口名
tcpdump -i eth0 -nn host 192.168.2.100 # 只抓与某主机
tcpdump -i eth0 -nn port 69 # 抓 TFTP(69)
tcpdump -i any -nn icmp # 抓所有接口的 ping
tcpdump -i eth0 -w cap.pcap # 存文件,拿到 PC 用 Wireshark 看
虚拟机设置静态 IP(netplan,Ubuntu 开发主机)
⚠️ 改错会导致虚拟机断网。
sudo vi /etc/netplan/01-network-manager-all.yaml
network:
version: 2
ethernets:
ens33: # 替换为你的实际网卡名(ip a 查看)
dhcp4: no
addresses:
- 192.168.2.222/24 # 静态 IP / 掩码
routes:
- to: default
via: 192.168.2.2 # 默认网关
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
sudo netplan apply
待确认 / 注意:旧写法
gateway4: 192.168.2.2在 Ubuntu 22.04+(netplan 0.103+)已弃用,改用上面的routes:。若文件名含network-manager且系统用 NetworkManager 渲染,networkd 语法未必生效,以实际renderer为准。
文件传输与共享
tftp(板子 ↔ PC)
(BusyBox)板子端上传/下载文件:
tftp -p -l /etc/smart_home.conf -r smart_home.conf 192.168.2.100
-pPUT(上传)/-gGET(下载)-l板子上的本地文件 /-rPC 端 TFTP 目录里的文件名 / 末尾是 PC 的 IP
环境差异:以上是(BusyBox)tftp 语法;Ubuntu 的
tftp-hpa客户端语法不同(交互式或-m)。
PC 端(Ubuntu)tftpd-hpa 服务配置:
sudo vi /etc/default/tftpd-hpa
TFTP_DIRECTORY="/srv/tftp"
TFTP_OPTIONS="--secure --create"
--create:允许新建文件(板子 PUT 上传时必须)
sudo systemctl restart tftpd-hpa
NFS(Ubuntu 做 server,板子挂载)
# 1. 安装
sudo apt update && sudo apt install nfs-kernel-server
# 2. 创建并放开共享目录
mkdir -p ~/nfs_share && chmod 777 ~/nfs_share
# 3. 配置导出
sudo vi /etc/exports
/home/meian/nfs_share 192.168.2.0/24(rw,sync,no_root_squash,no_subtree_check)
⚠️ 选项之间用英文逗号分隔,不能用中文逗号
,,否则导出解析失败。
rw可读写 /sync同步写 /no_root_squash保留客户端 root 权限 /no_subtree_check不检查子目录树
# 4. 刷新并查看
sudo exportfs -ra
showmount -e
# 服务管理
sudo systemctl status nfs-kernel-server
sudo systemctl restart nfs-kernel-server
板子端挂载见 mount。
Samba(Ubuntu 共享给 Windows/局域网)
sudo vi /etc/samba/smb.conf
[work]
path = /home/plh/work
browseable = yes
writable = yes
available = yes
comment = smb share
sudo systemctl restart smbd
sudo smbpasswd -a plh # 创建 Samba 用户
[work]:共享名(客户端"网络邻居"里看到的名字)path:服务器上真实共享目录browseable:yes出现在网络列表;no需手动\\server\work访问writable = yes(等价read only = no):允许写/改/删
systemd 与系统服务
systemd 是系统启动后第一个进程(PID 1),负责启动和管理服务(daemon)。以下以 mihomo 代理为例。
1. 准备可执行文件(示例)
gzip -d mihomo-linux-amd64-compatible-v1.19.21.gz
mv mihomo-linux-amd64-compatible-v1.19.21 mihomo
chmod +x mihomo
sudo mv mihomo /usr/local/bin/
2. 编写 service 文件
sudo vi /etc/systemd/system/mihomo.service
[Unit]
Description=Mihomo Daemon
# 在网络就绪后再启动(代理依赖网络)
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/mihomo -d /home/meian/.config/mihomo
# 进程退出后自动重启
Restart=always
# 重启前等待秒数
RestartSec=3
# 不输出日志到终端
StandardOutput=null
StandardError=null
[Install]
WantedBy=multi-user.target
⚠️ systemd 不支持行内注释。注释必须独占一行、以
#开头。像Type=simple#服务类型、After=network.target # ...这类写法会把注释解析进值里导致出错。
字段含义:
[Unit]:描述、依赖、启动顺序(After=只定义顺序,不代表强依赖)[Service]:怎么启动/重启ExecStart:启动命令,必须绝对路径Restart=always:进程挂掉自动重启RestartSec:重启前等待秒数(不是最大重启次数;限制次数用StartLimitBurst/StartLimitIntervalSec)
[Install]→WantedBy=multi-user.target:enable时把本服务挂到"多用户模式"这个 target 下,实现开机自启(不是"挂载点")
3. 启动与管理
sudo systemctl daemon-reload # 改动 unit 文件后必须执行
sudo systemctl start mihomo # 启动
sudo systemctl enable mihomo # 开机自启
sudo systemctl stop mihomo # 停止
sudo systemctl restart mihomo # 重启
sudo systemctl status mihomo # 查看状态
daemon-reexec(重启 systemd 自身)一般不需要,改 unit 用daemon-reload即可。
设备管理:udev 与 uevent
内核通过 Netlink 协议族的 NETLINK_KOBJECT_UEVENT 类型,向用户态广播内核对象(kobject)相关的设备事件(uevent),用于热插拔监听。
uevent 消息格式
每条 uevent 是一组键值对,用 \0 分隔,核心字段:
ACTION:add/remove/change等DEVPATH:sysfs 路径,如/devices/pci0000:00/.../usb1/1-1SUBSYSTEM:所属子系统,如usb/block/input- 设备属性:如
DRIVER=usb-storage、PRODUCT=1234/5678(厂商/产品 ID) - 其他元数据:如
ID_SERIAL、DEVNAME=sdb
ACTION=add\0
DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-1\0
SUBSYSTEM=usb\0
DRIVER=usb-storage\0
PRODUCT=1234/5678\0
...\0
监听代码(C++)
要点:socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT) 建套接字,nl_groups=1 订阅内核 uevent 组,SO_RCVTIMEO 设接收超时避免完全阻塞。
_hotplug_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
if (_hotplug_fd < 0) {
DEBUG_ERROR("Failed to create netlink socket: %s", strerror(errno));
_hotplug_running.store(false);
return;
}
nladdr.nl_family = AF_NETLINK;
nladdr.nl_pid = getpid();
nladdr.nl_groups = 1; // NETLINK_KOBJECT_UEVENT 组
if (bind(_hotplug_fd, (struct sockaddr*)&nladdr, sizeof(nladdr)) < 0) {
DEBUG_ERROR("Failed to bind netlink socket: %s", strerror(errno));
::close(_hotplug_fd);
_hotplug_fd = -1;
_hotplug_running.store(false);
return;
}
// 设置接收超时,避免完全阻塞
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
setsockopt(_hotplug_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
char buffer[4096];
while (_hotplug_running.load()) {
memset(buffer, 0, sizeof(buffer));
int len = recv(_hotplug_fd, buffer, sizeof(buffer) - 1, 0);
if (len < 0) {
int err = errno;
if (err == EAGAIN || err == EWOULDBLOCK) continue; // 超时,继续
if (err == EINTR) continue; // 被信号中断,继续
if (!_hotplug_running.load()) break; // 正常退出
DEBUG_ERROR("recv error: %s (errno=%d), will retry", strerror(err), err);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
if (len == 0) { // socket 被关闭
if (!_hotplug_running.load()) break;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
parseUevent(buffer);
}
if (_hotplug_fd >= 0) {
::close(_hotplug_fd);
_hotplug_fd = -1;
}
交叉编译工具链
快速配置工具链
写进 ~/.bashrc,用时执行 rk3588 一键设置 ARCH / CROSS_COMPILE / PATH:
rk3588() {
# 按你的实际路径改
local TC_ROOT="$HOME/work/toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu"
local TC_BIN="$TC_ROOT/bin"
export ARCH=arm64
export CROSS_COMPILE=aarch64-none-linux-gnu-
export PATH="$TC_BIN:$PATH"
echo "ARCH=$ARCH"
echo "CROSS_COMPILE=$CROSS_COMPILE"
echo "TOOLCHAIN_BIN=$TC_BIN"
which "${CROSS_COMPILE}gcc" || return 1
"${CROSS_COMPILE}gcc" -v 2>&1 | head -n 3
}
查看工具链支持的 C++ 版本
# 看默认 C++ 标准(__cplusplus 宏)
g++ -dM -E -x c++ /dev/null | grep __cplusplus
# 199711=C++98 201103=C++11 201402=C++14 201703=C++17 202002=C++20
# 测试是否支持某标准(能编过即支持)
g++ -std=c++17 -x c++ -fsyntax-only /dev/null && echo OK
原写法
g++ -std=c++11 -E - < /dev/null只能验证"是否接受-std=c++11",不能直接看出默认/最高标准,用上面的__cplusplus更直观。交叉工具链把g++换成${CROSS_COMPILE}g++。
nm
列出二进制文件(可执行/库/目标文件)中的符号表。分析目标平台产物时用交叉版 ${CROSS_COMPILE}nm。
aarch64-none-linux-gnu-nm -D -C libfoo.so
-D:只显示动态符号。库被 strip 后,不加-D可能看不到任何输出;加-D才能看到对外暴露的接口-C:demangle,把 C++ 修饰后的符号名还原成可读形式
常见故障与排查案例
排查速查对照
| 现象 | 先看什么 | 命令 |
|---|---|---|
| 磁盘满 / 写失败 | 磁盘、inode 占用 | df -h、df -i、du -h -d1 |
| 内存不足 / OOM | 真正可用内存 | free -h(看 available) |
| 系统卡、I/O 慢 | I/O 瓶颈 | iostat(看 %iowait) |
| 加了磁盘但系统没变大 | 分区/文件系统未扩展 | 见下方「虚拟机磁盘扩容」 |
| 设备插拔无反应 | 热插拔事件 | 监听 uevent,见 设备管理 |
| 板子 TFTP 上传失败 | 服务端未允许新建文件 | tftpd-hpa 加 --create |
| systemd 服务不生效 | 改 unit 未重载 / 行内注释 | daemon-reload;注释必须独占一行 |
虚拟机磁盘扩容
现象:在 VMware 给虚拟机硬盘"加了 20 GB",只是把底层 .vmdk 拉大了,Ubuntu 里的系统并不知道,需要手动扩分区 + 扩文件系统。
⚠️ 对正被使用的根分区做 parted/resizepart 有风险,务必先确认分区号,最好有快照/备份。
1. 确认磁盘已变大、分区还是旧大小
sudo fdisk -l /dev/sda
# Disk /dev/sda:200 GiB ...
# /dev/sda1 * 2048 377487359 377485312 180G 83 Linux ← 分区仍是旧大小
2. 用 parted 把分区扩到磁盘末尾 ⚠️
sudo parted /dev/sda
(parted) print free # 查看末尾的可用空间
(parted) resizepart 1 100% # 把 1 号分区扩到 100%
# 警告: 分区正被使用。是否继续? → yes
(parted) quit
3. 扩展文件系统到分区大小
# ext2/3/4:
sudo resize2fs /dev/sda1
# xfs(若根是 xfs):
# sudo xfs_growfs /
df -h / # 确认已生效
说明:分区表变化后可能需要检查
/etc/fstab。resize2fs可在线扩展已挂载的 ext4;缩小则必须先卸载。