简介:面向Linux系统上实现BLE外围设备功能的开发资料包,对应标签“linux ble periph”,适合嵌入式或物联网开发者、以及需要让Linux设备通过Smart App接收BLE消息的工程师。内容围绕如何将Linux配置为蓝牙低功耗外设,涵盖配置、编译、GATT服务等环节。压缩包含932个文件,整体3.57MB,以C语言源码和头文件为主(422个c、257个h),同时包含配置文件、Makefile、shell脚本及txt文档,可支持从编译构建到运行调试的完整流程,也兼顾了BlueZ协议栈常见组件的参考实现。目前已有386人学习下载。资料中可见大量测试示例和服务端/客户端用例,例如GATT server/client、广播示例、蓝牙测试工具等,可帮助理解BLE角色配置、服务广播与数据收发机制;文档与说明文件则可用于核对编译依赖与系统配置。整体体量紧凑,适合快速搭建Linux BLE外围设备开发环境并对照实践。
1. files.tar.gz 到底是什么:一个文件装下整个目录的约定
接手一台旧服务器时,最常看到的就是 files.tar.gz;从网上下载源码包,解压出来也常叫这个名字。它不是一个固定项目的产物,而是一类归档包的默认约定:先用 tar 把整个目录连同权限、属主、软链、硬链接装进一个数据流,再用 gzip 把这段流压成 .gz,于是就有了 files.tar.gz。这个文件解决三类问题:备份目录、分发源码、迁移数据。适合后端开发、运维,以及所有需要手动搬服务器文件的人。
tar 和 gzip 的分工经常被混为一谈,恰恰是很多解压翻车的根源。tar 负责格式化地"摆好"目录结构,gzip 负责"压小"体积;两者合并后,读取时必须从头流式解到尾,这决定了后面讲的很多边界行为。下面从打包、解压、增量备份、排错到复检,按你实际操作时会遇到的顺序展开;每段命令都能直接抄走,但更重要的是知道参数为什么存在。
2. 打包与压缩:tar -czf 最小命令与常用参数拆解
2.1 先分清 tar 和 gzip:一个只管归档,一个只管压体积
tar 的名字来自 tape archive,最初是磁带备份工具,设计目标是把若干文件连同目录结构、权限、时间戳、属主、硬链接关系写进同一个连续数据流。它本身不压缩,所以一个没压过的 tar 包经常比所有文件加起来还大一点,因为要额外记录元数据。真正把体积压下来的是 gzip,它把 tar 吐出来的连续流整体压缩。
理解这个分工能解释很多现象。比如 tar -tzf 能列出包内文件,但为什么不能像 zip 一样直接跳到某个文件?因为 .gz 是整段流式压缩,想读尾部必须从头解到尾部。再比如换压缩算法,只需要替换字母:-z 是 gzip,-j 是 bzip2,-J 是 xz,归档的使用方式不变,只是压缩率和速度不同。
那为什么不直接用 zip?zip 对每个文件单独压缩,中心目录区记录索引,随机读取很方便,但 zip 在 unix 世界保存权限位、符号链接的能力参差不齐,不同工具打出来的包行为经常是玄学。tar.gz 则完整保留 unix 属性,代价是变成顺序流。所以源码发布包、容器镜像层、服务器备份绝大多数都是 tar.gz,而不是 zip。
2.2 tar -czf files.tar.gz ./data:最小打包命令拆解
最常见的打包命令就是这一条:
tar -czf files.tar.gz ./data拆开看:-c 表示创建归档;-z 表示用 gzip 压缩;-f 后面必须紧跟归档文件名;最后的 ./data 是你要打包的源路径。注意 -f 的位置:它和文件名是一对,必须写在组合参数里靠近末尾的位置,写成 tar -czf 是惯例,写 tar -fcz 也能跑,但读起来容易看晕,我习惯固定用 -czf。
如果打包时想看到每个文件,加一个 -v:
tar -czvf files.tar.gz ./data-v 会把正在归档的文件名逐个打印到终端,文件多的时候输出非常吵。脚本里我一般不加 v,手动打包时加一个确认进度。真正常用的是排除某些目录:
tar -czf files.tar.gz ./data \ --exclude='./data/logs' \ --exclude='./data/.cache'这段命令里 --exclude 后面跟的是包内路径,不是磁盘绝对路径。所以打包 ./data 时,排除项要写 ./data/logs,而不是 /srv/app/data/logs。这个细节特别容易错:从根目录打包和从上层目录打包,同一个目录在包里的路径不一样,排除规则也要跟着变。
2.3 保留权限、硬链接与符号链接:备份类归档的关键参数
tar 比 zip 强的地方在于,它默认就会记录权限位、属主、时间戳、符号链接和硬链接关系。但"记录"和"恢复"是两件事。
创建归档时,权限和时间戳一定被记录,不需要额外参数;符号链接默认只记录链接本身,不打包链接指向的内容,所以解压出来还能保持软链关系。如果你加了 -h(--dereference),tar 会把符号链接指向的目标文件内容也打进去,这会让包体积膨胀,处理不好还会循环,我一般不加。硬链接则不需要任何参数,tar 自动识别同一 inode 只存一份数据,解压时恢复成硬链接关系。
解压时是否恢复权限,取决于你以什么身份运行 tar:root 默认恢复归档里记录的权限和属主;普通用户默认不恢复属主,权限还要受当前 umask 影响。需要强制保持一致性时,用下面这个组合:
tar -xzf files.tar.gz -C /opt/app --same-permissions如果反过来,你想让解出来的文件归当前用户所有,用 --no-same-owner。一个常见做法是:从其他机器拷来的备份包,解压时直接用 --no-same-owner,避免把原机器的 uid 数字带过来。参数本身很简单,但配合场景选对,才不会被文件的属主和权限坑到。
2.4 排除与白名单:--exclude-from 与 --files-from 的组合打法
备份一个大型项目时,node_modules、.git、临时文件通常都要排除。规则多的时候,命令行里写一串 --exclude 不清爽,我会先把规则写进文件:
cat > /tmp/backup.exclude <<'EOF' ./data/node_modules ./data/.git *.log EOF tar -czf files.tar.gz -X /tmp/backup.exclude ./data-X 是 --exclude-from 的缩写,文件里每行一条匹配规则,和命令行的 --exclude 规则一样,都作用于归档内路径。反过来的场景是只想打包某几个文件,比如只备份配置文件:
find /srv/app -name '*.conf' -type f -print > /tmp/file.list tar -czf files.tar.gz -T /tmp/file.list-T 是 --files-from,意思是"从这个列表里读取要打包的文件"。列表里可以是绝对路径,tar 打包时会自动处理路径前缀。如果列表想通过管道传给 tar,就用 -T -,从 stdin 读。这个排错技巧值得记住:不要直接在命令行手写几十个文件,维护一个清单文件,比反复改命令可靠得多。
3. 解压 files.tar.gz 之前,先回答三个问题:路径、权限与选择性
3.1 先 tar -tzf 看清单,再 tar -xzf 动手
拿到一个陌生的 files.tar.gz,我做的第一件事永远是"只看不解":
tar -tzf files.tar.gz | head -n 20 tar -tzf files.tar.gz | wc -l tar -tzf files.tar.gz | grep -E '(^|/)\.git/'第一条列出前 20 个成员,快速看包内有没有顶层目录;第二条统计成员总数,判断包的大小和文件数量是否合理;第三条检查是否夹带了不想解出来的内容。-t 表示列出,不解包,配合 -z 自动解压 gzip 流;这一步不会修改当前目录,十分安全。
想看更详细的信息,用 -tvzf:每个文件的权限、属主、大小、修改时间都会显示,和 ls -l 的输出格式很像。这里有个实际好处:解压之前就知道包里有没有 setuid 程序、有没有奇怪的属主、有没有超大的文件,给后续决定添加哪些解压参数提供依据。
3.2 路径穿越与绝对路径:为什么现代 tar 会报 Removing leading
tar 归档里存的是"路径名字符串",恶意或损坏的归档可以把路径写成 ../../tmp/evil,或者 /etc/cron.d/evil。如果在不可信来源的包上直接解压,就有路径穿越风险。GNU tar 默认做了两件事:把归档里的绝对路径去掉前导 /,遇到包含 .. 的路径则跳过该成员并打印警告。所以常见的 "tar: Removing leading `/' from member names" 不是错误,恰恰是安全机制在工作。
解压之前,可以用一行 awk 主动检查包内路径:
tar -tzf files.tar.gz | awk '/^\// || /(^|\/)\.\.\// {print}'没有输出说明路径还算干净。真正解压时,我更建议先解到临时目录,确认内容再决定放哪:
mkdir -p /tmp/safe_extract tar -xzf files.tar.gz -C /tmp/safe_extract如果包里本来就带着顶层目录 ./data,解出来会落在 /tmp/safe_extract/data。很多部署包的顶层目录对目标环境没有意义,这时候用 --strip-components=1 可以直接剥掉第一层。
3.3 解压的四个常用参数:-C、-k、--strip-components、--no-same-owner
下面这张表覆盖了我日常解压 files.tar.gz 时用到的绝大部分参数:
| 参数 | 作用 | 典型场景 |
|---|---|---|
| -C DIR | 先切到 DIR 再解包 | 固定解压到目标目录 |
| -k | 已存在的文件不覆盖 | 避免旧包覆盖新文件 |
| --strip-components=N | 剥掉前 N 层路径 | 去掉包内顶层目录 |
| --no-same-owner | 解出的属主设为当前用户 | 跨机器部署 |
把它们组合起来,就是部署现场最常见的命令:
tar -xzf files.tar.gz -C /opt/app --strip-components=1 --no-same-owner逻辑是:把包解开到 /opt/app,去掉包内第一层目录,同时避免 root 把归档里记录的 uid 恢复成原机器的数字。注意 -k 与 --overwrite 正好相反:-k 跳过已存在文件,--overwrite 强制覆盖。默认行为是"有就覆盖且不提示",这个默认值坑过不少人,后面避坑章会说。
3.4 选择性解压与按需取文件
有时整个包不需要全解,只要抽出某一个配置文件。tar 支持按包内路径选择性解压:
tar -xzf files.tar.gz ./data/app/config.yaml -C /tmp/config-check路径必须和包内路径完全一致,所以先 tar -tzf 确认路径名,否则会报 Not found in archive。抽出到 /tmp/config-check 后,用 diff 和现网配置做对比,再决定是否替换。
批量抽某一类文件时,配合通配符:
tar -xzf files.tar.gz --wildcards './data/*.yaml' -C /tmp/yaml-check这里的通配符必须用引号包起来,否则 shell 会先展开,把当前目录里的 .yaml 文件列表传给 tar,结果完全不是你要的。--wildcards 让 tar 在包内路径上做模式匹配,而不是精确匹配单个成员。
4. 增量备份与流式处理:把 tar.gz 从一次性归档变成生产线
4.1 GNU tar 增量备份:--listed-incremental 从 level 0 开始
tar.gz 不只是打一次包,它还常被用来做每日备份。GNU tar 提供了一套基于快照文件的增量机制,核心是一个 .snar 快照文件,记录扫描过的每个文件的 mtime、大小、inode。第一次做全量,后续做增量:
tar --listed-incremental=/var/backup/snap.snar --level=0 -czf full_0.tar.gz ./data tar --listed-incremental=/var/backup/snap.snar -czf inc_1.tar.gz ./data第一条命令里 --level=0 明确指定这是全量备份,同时生成快照文件;第二条不带 --level,由于快照已存在,tar 默认从 level 1 开始做增量,只打包在上一次之后发生变化的文件。增量包小很多,数据目录一天只改几百兆的话,inc_1.tar.gz 通常只有全量的零头。
快照文件是这套机制的心脏。它必须和归档一起妥善保存,丢了快照,后续的增量包全部失效。而"失效"不是解压报错,是静默地缺文件,这一点在恢复时才暴露,特别坑。
4.2 恢复增量的正确顺序:先基础包,再按时间顺序叠加
恢复增量备份不需要快照文件,只需要归档本身,但顺序必须严格:
mkdir -p /restore/data tar -xzf full_0.tar.gz -C /restore tar -xzf inc_1.tar.gz -C /restore先解全量基础包,再按时间顺序解增量包。顺序反了,增量会把基础包里的文件覆盖成旧状态,或者报文件不存在,结果完全不可用。恢复后建议用 diff -r 和源目录做一次对比验证,不要只看文件数量。
这里必须说清楚 tar 增量备份的边界:它记录的是"文件发生了什么变化",不是数据库级别的连续日志,也不擅长记录删除事件。如果源目录在两次备份之间删除了几个文件,增量包不会记住"这里少了一个文件",恢复出来反而会多出这些文件。所以涉及重要数据时,我会每周强制做一次 --level=0 全量,每天走增量,不给删除操作留太多空隙。
4.3 流式输出:tar -cf - 接 pigz、gpg 与 split
tar 真正厉害的地方在于流式处理。-f - 表示归档写到标准输出,而不是文件。这样后面可以接任意程序,先看两行示例:
tar -cf - ./data | pigz -4 > files.tar.gz tar -cf - ./data | gpg -c --batch --passphrase 'backup-key' -o files.tar.gz.gpg第一条里 -cf - 把未压缩的 tar 流喂给 pigz,pigz 是 gzip 的多线程实现,-4 是压缩级别,多核机器能明显感受到速度提升;第二条把 tar 流加密成 gpg 文件,适合备份到不受信任的存储位置。tar 本身带的 -z 用的是单线程 gzip,数据量大时 CPU 单核跑满,换 pigz 是最划算的优化。
流式打包还能配合 split 解决大包的搬运问题:
tar -czf - ./data | split -b 1G - files.tar.gz.part_ cat files.tar.gz.part_* | tar -xzf - -C /restoresplit 按 1GB 切块,适合拷贝到 exFAT 这类有单文件大小限制的移动介质;还原时 cat 把所有分片合并,通过 stdin 喂给 tar。整个链路没有落地中间文件,不用占双倍磁盘。
4.4 从 stdin 解包:管道另一端接什么,怎么验证
既然可以流式打包,自然可以流式解包。管道场景里,归档文件不一定是磁盘上的实体,可能来自 cat、网络传输或加密层解密后的输出:
cat files.tar.gz | tar -xzf - -C /tmp/check这里 tar 的文件名参数是 -,表示从 stdin 读取归档。要注意的是性能行为:tar 从 stdin 读时无法预知归档总长度,也无法像文件模式那样回跳。像 --delete 这类需要随机访问的操作,在 stdin 模式下不可用。这也是很多人在管道里试图删包内文件失败的原因。
管道另一端的输出可以先验证再解压,避免把问题流解开:
tar -tzf - < files.tar.gz | head -n 5用重定向而不是管道,语义更清晰:文件被打开后连接到 tar 的 stdin,只列出前 5 个成员。看到内容符合预期,再决定是否真正解包。这个习惯成本极低,但能拦下大量来源不明的归档。
5. tar 排错与避坑:5 个让备份翻车的真实案例
5.1 磁盘明明还有几个 G,解压却报 No space left
现象:解压一个包含几十万小文件的源码包,解到一半突然报 No space left on device,但 df -h 看磁盘还有好几 GB。
原因:inode 耗尽了。每个文件都要占用一个 inode,而 inode 总数在格式化时固定。几十万个小文件会瞬间吃光 inode,df -h 看的是块容量,根本反映不出这个问题。
解决:先确认是不是 inode 问题:
df -i /restoreIUse% 接近 100% 就可以断定。清理无关的临时目录,或者把大量小文件合并成少量大文件。如果这个目录是长期备份目标,重新格式化时把 inode 密度调大,比事后删文件靠谱得多。
5.2 解压后文件属主变成数字,或变成 root
现象:从 A 机器打包的 files.tar.gz,到 B 机器解压,ls -l 看到一堆 1000:1000,有时直接变成 root:root。
原因:tar 记录的是数字 uid/gid,不是用户名。跨机器时用户 id 不一致,系统无法映射成名字,就显示数字;用 root 解压时,tar 默认会把归档里的 uid 原样恢复,于是变成别的机器的 root 或未知用户。
解决:部署场景统一加 --no-same-owner。需要固定属主时,直接指定:
tar -xzf files.tar.gz -C /opt/app --no-same-owner --owner=app --group=app--owner 和 --group 在解压时强制覆盖归档里的属主信息。这个组合在容器镜像构建、应用发布目录里非常常用,比解压后再 chown -R 少一步且更确定。
5.3 同名文件被覆盖,没有任何提示
现象:想用备份包补几个文件,结果整个目录被旧包覆盖,且终端没有任何提示。
原因:tar 解压的默认行为是"已存在的同名文件直接覆盖,不询问"。这既是优点也是坑,尤其在你不确定包里文件版本的情况下。
解决:用 -k(--keep-old-files)让已存在文件跳过:
tar -xzf files.tar.gz -C /opt/app --keep-old-files更稳妥的做法是先解到新目录,diff 之后再同步:
mkdir -p /tmp/app_new tar -xzf files.tar.gz -C /tmp/app_new diff -r /opt/app /tmp/app_newdiff 输出就是后悔药:哪些文件会被改动、哪些是多出来的,一目了然。凡是往生产目录解压,我都会先这么过一遍。
5.4 增量备份恢复完,文件对不上
现象:按顺序恢复了 full_0.tar.gz 和 inc_1.tar.gz,结果某些新文件没有出现,某些删掉的旧文件反而回来了。
原因:要么恢复顺序反了,要么增量包对应的快照文件和归档不是同一套。快照文件一旦被另一次备份覆盖,后续增量记录就会错乱,而且恢复时不会报错,只会静默地给错误结果。
解决:备份时把快照文件命名并归档在一起,每次增量备份后复制一份快照到独立目录;恢复前先确认归档时间顺序,恢复目标使用空目录,不混旧文件。不要依赖"凭感觉"判断哪个包是最新的,文件名里带时间和层级是基本纪律。
5.5 归档里的文件名带换行,脚本 for 循环直接炸掉
现象:写了个 shell 脚本来统计包内文件数量,结果统计出的文件数翻了两三倍,日志里路径被拦腰截断。
原因:脚本写了 for f in $(tar -tzf files.tar.gz),shell 会按空格、换行、制表符分词,包内文件名一旦包含特殊字符,一个路径就被拆成多个变量。
解决:改用 while read 逐行处理:
tar -tzf files.tar.gz | while IFS= read -r f; do echo "$f" doneIFS= read -r 保证空格和反斜杠原样进入变量。另外有一个少有人知的参数 --verbatim-files-from:当用 -T 从文件读取打包列表时,tar 默认会解释列表里的反斜杠转义;如果文件名本身含反斜杠或换行,就会和预期不一致。加 --verbatim-files-from 后列表逐字照读,不再做任何解释。
6. 收尾技巧:用 gzip -t 与 tar -df 给归档做复检
6.1 五秒验证与差异比对
归档做完、传完、解完,都需要验证。我固定用三步:
gzip -t files.tar.gz tar -tzf files.tar.gz > /tmp/list.txt tar -df files.tar.gz -C /restoregzip -t 只验证 gzip 压缩流完整,不关心内部路径;tar -tzf 验证归档结构可读;tar -df 把归档里每个成员与 /restore 下的同名文件比较大小和 mtime,有差异会直接打印。这三步加起来五秒左右,能确认两件事:包没损坏、解开后的内容和包内记录一致。
实际部署时,我会把这些步骤写成一个最小脚本:
#!/usr/bin/env bash set -euo pipefail ARCHIVE="${1:-files.tar.gz}" DEST="${2:-/restore}" gzip -t "$ARCHIVE" tar -tzf "$ARCHIVE" > /dev/null tar -xzf "$ARCHIVE" -C "$DEST" --no-same-owner tar -df "$ARCHIVE" -C "$DEST" && echo "OK: $ARCHIVE"set -euo pipefail 保证任一步失败就退出,不会带着坏归档继续往下跑。--no-same-owner 避免把归档里的 uid 数字带到目标机器;如果目标环境需要保持原属主,去掉这个参数即可。
我的个人习惯是:任何 files.tar.gz 到我手上,先 tar -tzf 看第一屏内容,再决定后续参数。这个习惯帮我躲过好几次解错目录、覆盖生产配置的翻车。tar 本身不是一个智能工具,但因为简单可靠,反而能在服务器上活几十年;把上面的检查动作养成习惯,它就不会成为那个让你半夜爬起来恢复数据的黑匣子。希望帮到你。
本文还有配套的精品资源,点击获取