Shell脚本学习指南-第十章-文件处理
第10章 文件处理
本章目标:掌握UNIX/Linux下文件、目录常用工具:查看文件列表、修改时间戳、临时文件、查找文件、xargs、磁盘空间、文件对比。
10.1 列出文件
echo 通配展示文件
- 作用:shell先把通配符
*展开成文件名列表,echo只是打印文本;不校验文件是否真实存在。
⚠没有匹配文件时,shell不会展开,直接原样输出
*字符串。
ls 命令
- 作用:专门处理文件,校验文件是否存在,输出文件信息。
行为区别:输出到终端默认多栏;输出到管道/重定向,自动变成一行一个文件名。
| 选项 | 说明 |
|---|---|
-1 |
数字1,强制单栏输出 |
-a |
显示全部,包含.开头隐藏文件 |
-d |
只看目录本身信息,不列出目录里面内容 |
-F |
给不同类型文件末尾标记符号(/目录、*可执行、@软链接) |
-l |
小写L,长格式:权限、所有者、大小、修改时间 |
-i |
打印inode号 |
-L |
跟随软链接,显示链接指向的真实文件信息 |
-r |
反转排序 |
-R |
递归列出所有子目录 |
-S |
按文件大小从大到小排序(GNU) |
-t |
按修改时间,新的放前面 |
示例
ls -l 长格式输出字段拆解
|
|
- 第1字符:文件类型
-普通文件,d目录,l软链接 - 接下来9位:rwxr‑xr‑x 文件权限
- 链接计数
- 文件所有者
- 文件所属用户组
- 文件字节大小
- 修改时间
- 文件名
坑:文件名内部包含换行、tab特殊字符,ls终端会显示问号;通过管道
od -a才能看到真实字节。所以不要解析 ls 的输出用于脚本处理文件名!
创建一个名字带换行的测试文件
shell 中 $'\n' 代表换行字符。文件名:one+ 换行 +two
|
|
现在目录里存在一个文件,文件名不是字符串one\ntwo,是:
直接运行 ls,终端输出看到问号
|
|
终端输出(交互式终端,stdout 指向屏幕)
|
|
ls 输出交给管道,用 od‑a 看真实字节
|
|
输出类似:
|
|
10.2 使用touch更新修改时间
作用两件事:
- 文件不存在:创建空文件
- 文件已存在:更新文件的时间戳(访问/修改时间),不会改动文件内容
创建空文件多种方式对比
touch选项
| 选项 | 作用 |
|---|---|
-m |
只更新修改时间(默认) |
-a |
只更新访问时间 |
-t CCYYMMDDhhmm[.SS] |
指定时间戳 |
-r ref_file |
复制参考文件的时间戳到目标文件 |
示例
小知识:UNIX时间戳epoch,起点1970‑01‑01 00:00 UTC;32位时间到2038溢出;现代系统64位时间。GNU touch
-d可以解析人类可读日期,规避POSIX时间格式限制。
10.3 临时性文件的建立与使用
/tmp:开机经常清空;/var/tmp:重启数据保留;属于公共共享目录,写脚本要考虑安全问题。
方案1:$$ 进程ID生成临时文件(旧方案,不安全)
$$:shell内置变量,代表当前脚本进程PID。
⚠安全缺陷:文件名可以被猜测,存在攻击风险。
${TMPDIR-/tmp} 拆解
|
|
- 是这个语法的固定符号,不是减号!
格式:${TMPDIR - /tmp}(书写时中间不能有空格!必须写成 ${TMPDIR-/tmp})
规则
- 如果环境变量
TMPDIR已经定义、且不为空:就取变量本身的值。 - 如果
TMPDIR没定义,或者是空值:就使用-后面的字符串/tmp作为返回结果。
案例 1:没有设置 TMPDIR
输出:/tmp
变量不存在,使用
-后面的默认值/tmp
案例 2:设置 TMPDIR=/home/xxx/mytmp
输出:/home/zhangsan/mytmp
变量存在,直接拿 TMPDIR 本身的值,后面
‑/tmp完全被忽略。
完整拼接
|
|
容易混淆的几个shell替换语法(必考,shell脚本学习指南原文知识点)
| 语法 | 作用 |
|---|---|
${VAR‑word} |
VAR未定义/空,返回word;有值返回VAR;不会修改变量本身 |
${VAR=word} |
VAR未定义/空,返回word,并且把word赋值给VAR变量 |
${VAR:-word} |
和${VAR‑word}几乎一样;就算VAR=““空字符串,也使用默认(更严格) |
${VAR:=word} |
VAR为空/未定义,赋值word并返回 |
方案2:mktemp(推荐,安全,不可预测文件名)
作用:生成难以猜测的临时文件名/临时目录;自动设置权限,其他人无法访问。
模板中大写X会被随机字符替换;建议不少于12个X。
注意:HP‑UX原生mktemp实现有安全缺陷,建议替换OpenBSD版本。
方案3:/dev/urandom /dev/random
二者是 Linux 下特殊字符设备文件,读取它,会吐出原始二进制随机字节。
/dev/random:熵池不足就阻塞卡住不动,脚本不要用。/dev/urandom:永不阻塞,日常脚本优先选它。
⚠注意:输出是原始二进制,里面会有各种不可打印字节、NUL、换行,不能直接拿来当文件名,需要做编码转换,转成可见字符(十六进制 /base64)。
⚠重要注意事项
- 一定要加
head -c N限制读取字节数量! /dev/urandom 是无限数据流,不加 head 会源源不断读数据,脚本卡死。-c 16代表读取 16 字节,16 字节随机度已经很高。 - 不要直接把
/dev/urandom原始二进制字节用作文件名! 二进制里面可能出现/,而/在 linux 文件名中是非法字符,会直接报错。 必须转成十六进制或者 base64 可见字符串之后,再做文件名。
方式 1:od 转十六进制
od -x:把二进制字节转成十六进制字符串。
head -c 16:只读取 16 个字节,不要读无穷无尽的数据流!必须加,不然程序卡死一直读。
输出示例:
再搭配tr删掉空格换行,拼接成随机字符串:
tr '/+' '_-'命令详解
- 第一组(左边):
/和+两个字符 - 第二组(右边):
_和-两个字符
对应关系:
- 遇见字符
/→ 替换成下划线_ - 遇见字符
+→ 替换成横杠-
10.4 寻找文件
10.4.1 locate 快速查找
locate:依靠预生成数据库,不用遍历磁盘,查找速度飞快;数据库一般由cron定时任务
updatedb更新。
⚠缺点:只能查到数据库生成之前存在的文件;刚新建文件locate找不到;安全版本slocate会过滤无权限访问的文件。
10.4.2 type:查看命令真实身份
shell内置命令,识别命令:外部程序、别名、shell函数。
10.4.3 find命令(最强大,磁盘实时遍历)
find语法:
find [查找起始目录] [测试条件] [操作]
- 特点:自动递归子目录;不会忽略
.开头隐藏文件;输出不排序; - 逻辑:挨个遍历目录下每一个文件,执行测试条件;条件成立执行操作;默认操作打印文件名。
| 常用选项 | 说明 | 示例 |
|---|---|---|
-name 'pattern' |
匹配文件名,通配符一定要引号保护,防止shell提前展开 | find . -name '*.txt' 查找当前目录下所有后缀为txt的文件 |
-type f/d/l |
f普通文件;d目录;l软链接 |
find . -type f 只找普通文件,排除目录、软链接 |
-user username |
匹配文件所有者 | find /home -user root 查找home下属主为root的文件 |
-group groupname |
匹配文件所属用户组 | find . -group devel 查找组为devel的文件 |
-perm mode |
权限匹配; ‑perm -002代表其他人可写 |
find . -perm -002 查找其他用户拥有写权限的文件 |
-size n[c/k] |
文件大小;c字节,kKB; +大于,‑小于 |
find . -size +1024k 查找大于1MB的文件 |
-mtime n |
文件修改时间,单位天; ‑n少于n天;+n大于n天 |
find . -mtime -7 查找7天之内修改过的文件 |
-atime n |
文件访问时间(被读取) | find . -atime +30 查找超过30天没有访问过的文件 |
-ctime n |
inode属性变更时间(权限、属主变化) | find . -ctime -1 1天内inode信息发生变动的文件 |
-ls |
输出类似ls‑li的详细信息 |
find . -type f -ls 找到文件并打印详细元数据 |
-prune |
裁剪目录,不向下递归进入该目录 | find . -path ./temp -prune -o -print 跳过temp目录,不进去搜索 |
-follow |
遍历的时候跟随软链接,访问链接指向真实文件 | find . -type l -follow 找到软链接并且追踪到真实文件 |
时间正负号规则:
-n:少于n天;+n:大于n天;不带符号正好n天。
布尔运算符:
-aAND(默认,多个条件写一起就是and)-oOR!NOT 取反
示例
find搭配while read处理文件:注意文件名含换行特殊字符风险;GNU find提供
‑print0,搭配xargs ‑0安全处理任意文件名。
find -print0 安全处理特殊文件名
小记忆技巧
-print0:print 输出,使用 0 字节 (NUL) 做分隔符,所以叫 print0,连写。-0:xargs 接收 0 字节分隔的数据。
10.5 执行命令 xargs
解决什么痛点
shell命令行有最大长度限制ARG_MAX;当$(find …)替换输出文件太多,报错 Argument list too long。
xargs:读取标准输入,把输入分批组装参数,调用后面的命令。
基础示例
/dev/null小技巧:保证grep至少两个参数,每一条匹配行都会打印文件名;没有输入的时候xargs不会执行命令。
GNU扩展:xargs -0 配合find -print0,安全处理带空格、换行文件名
|
|
对比:
$(find …):一次性全部参数,参数过长报错;文件名换行容易破坏解析find | xargs:自动分批;特殊文件名有坑;find -print0 | xargs -0:最安全,GNU工具集支持。
xargs 命令详解
xargs 通俗讲解
先看整条命令:
|
|
一、管道 | 作用
find 运行完,把找到的一堆文件名,一行一个输出到标准输出;
管道 |,把 find 的输出,直接送给右边 xargs 的标准输入。
⚠重点:xargs 不会把管道过来的内容,当做后面命令的标准输入。
xargs:读取管道过来的文本,把文本拆分成一个个参数,追加到后面命令的参数列表上,然后执行这个命令。
二、拆解上面这条命令
find /usr/include -type f遍历/usr/include,找出所有普通文件,输出:
每一个文件名占一行。
- 管道把上面一大串文件名全部交给 xargs。
xargs拿到这一堆文件名,做两件事:
① 把一行行的内容,拆分成一份份文件名;
② 把这些文件名,当做参数追加给grep命令,然后调用grep。
原始写的命令:
|
|
grep固定参数:POSIX_OPEN_MAX、/dev/null- xargs把管道读到的文件名,追加到末尾。
三、xargs 会实际分批执行类似下面的命令
假设find输出非常多文件,系统命令行装不下全部,xargs就会分组分批调用grep。
第一批:
|
|
用完这一批名额,再执行第二批:
|
|
这就是所谓:输入分组,调用后面的命令 xargs自动根据系统
ARG_MAX限制,把长长的文件名列表切分成多组,多次运行grep,避免Argument list too long参数过长报错。
四、/dev/null 小技巧解释
grep语法:
|
|
- 多个文件参数的时候,匹配行会打印出文件名:匹配内容。
- 如果只给grep一个文件参数,grep就不打印文件名,只输出匹配内容,分不清来自哪个文件。
我们传给grep:/dev/null + 一堆头文件。
/dev/null是一个永远空的虚拟文件,它不会产生任何匹配;但它占了一个参数位置。
于是grep永远至少有两个文件参数,每一条匹配结果都会带上文件名。
grep 有个行为:
- grep 关键字 单个文件:只给 1 个文件,匹配输出的时候,不打印文件名,只输出匹配到的行
- grep 关键字 文件 A 文件 B:传≥2 个文件,输出格式是
文件名:匹配行,带上来源文件名。
模拟:
|
|
五、对比不用xargs的写法(有坑)
|
|
$(find …) 命令替换:把find全部输出,一次性全部变成grep参数。
- 文件数量少:正常工作。
- 文件数量巨大:参数列表超出系统上限,直接报错
Argument list too long。
xargs 就是用来解决这个痛点:自动分批,多次执行命令。
六、举个极简小实验,方便你肉眼看懂xargs在干什么
准备3个文件
六、测试1
|
|
find输出:
xargs读取,把文件名追加到echo后面,实际执行:
|
|
屏幕输出:
|
|
管道进来的内容,变成命令的参数,不是命令输入。
七、容易混淆重点(非常多人踩坑)
-
cat file | xargs command管道内容 → 变成command的命令行参数。 -
cat file | command管道内容 → 变成command的标准输入。
八、对比两条命令,看差别
运行输出:
九、风险:文件名带空格、换行
普通 find | xargs 按换行切割;
如果文件名内部自带换行/空格,会切错。
安全版本:find -print0 | xargs -0,用NUL字节分割,彻底避开换行问题。
十、小结
- xargs读取管道输入,把输入内容拆分成参数,追加到后面命令的末尾,执行命令。
- 文件太多的时候,xargs自动分成多组,多次运行命令,规避参数长度超限。
- 普通管道
cmd1 | cmd2:数据给cmd2做标准输入;
cmd1 | xargs cmd2:数据变成cmd2的命令行参数。 - 处理任意奇葩文件名,务必使用
-print0+-0配对。
10.6 文件系统的空间信息
df 查看文件系统整体磁盘占用
disk free,查看各个挂载分区总、已用、可用空间。
| 选项 | 说明 |
|---|---|
‑k |
输出单位KB |
‑h |
human‑readable,人类可读单位(G/M),GNU |
‑l |
只看本地磁盘,排除网络文件系统 |
‑i |
查看inode使用情况 |
du 统计目录占用空间
disk usage:统计指定目录树实际占用磁盘块大小。
| 选项 | 说明 |
|---|---|
‑k |
单位KB |
‑s |
只输出总汇总,不输出子目录每一项 |
‑h |
人类可读格式(GNU) |
注意:du统计会遇到权限不足报Permission denied,统计结果不完全;硬链接不会重复统计。
10.7 比较文件
cmp:二进制比较
- 作用:逐字节对比两个文件,找到第一个不一样的位置就停止。
- 退出码:0相同;1不同;>1出错。
diff:文本文件差异
- 作用:对比两个文本,输出行级别差异;输出格式可给patch工具使用。
规则:
diff 旧文件 新文件输出标记:
<代表 左边第一个文件(旧文件 test.1) 的行>代表 右边第二个文件(新文件 test.2) 的行aadd 新增;ddelete 删除;cchange 修改
第一步:创建两个测试文件
test.1(旧文件)
test.2(新文件)
文件差异说明
- 旧文件第 3 行:
橙子→ 修改成了芒果(change 修改 c) - 新文件末尾多了一行:
西瓜(add 新增 a)
终端执行命令
|
|
diff 输出结果
逐行拆解输出
|
|
- 左边
3:旧文件 test.1 的第 3 行 c= change 修改- 右边
3:新文件 test.2 的第 3 行
含义:旧文件第 3 行发生修改,对应新文件第 3 行
|
|
< 代表这一行来自左边旧文件 test.1,旧内容是:橙子
|
|
分割线,把旧内容、新内容隔开
|
|
> 代表这一行来自右边新文件 test.2,新内容是:芒果
|
|
4:旧文件的第 4 行之后a= add 新增5:新文件第 5 行
含义:在旧文件第 4 行的后面,新文件增加了第 5 行
|
|
> 新增的内容来自新文件:西瓜
再演示一个删除 d 的例子
新建文件: test.old
test.new
删掉了李四这一行
运行
|
|
输出:
解析:
2d1:旧文件第 2 行被删除,对应新文件第 1 行之后< 李四:被删掉的那一行,来自左边旧文件
patch 打补丁演示(承接第一个橙子芒果西瓜例子)
把 diff 输出保存成补丁文件
test.patch 内容就是刚才 diff 输出全部文本。
执行 patch,把旧文件 test.1 更新成新文件内容
执行完之后 test.1 内容就变成和 test.2 一模一样:
diff -c 上下文模式(context 差异)
diff -c test.1 test.2 上下文格式输出
*** 后面:旧文件 test.1;---后面:新文件test.2。
| 符号 | 含义 |
|---|---|
! |
这一行发生修改 change |
+ |
新文件新增的行 add |
‑ |
旧文件要删掉的行 delete |
| 空格 | 没有改动的上下文行 |
文件校验和 md5sum / sha256sum
作用:根据全部文件字节算出哈希指纹;快速判断两个文件内容是否完全一样;下载文件校验完整性。
|
|
示例脚本:利用awk统计md5,找出内容完全一样的文件。
GPG数字签名
md5/sha只能校验文件有没有被无意损坏;数字签名可以验证文件来源身份,确认文件没有被恶意篡改。
- 私钥:签名用,只有发布者持有
- 公钥:所有人用来验证签名。
简单示例
10.8 小结
ls查看文件;touch创建空文件、修改时间戳。- 临时文件:优先
mktemp;不要直接用$$生成文件名,有安全风险;脚本退出用trap清理临时文件。 - 查找文件:
locate:查数据库,速度快,非实时;find:实时遍历磁盘,功能强大;处理特殊文件名务必使用‑print0 + xargs ‑0。
xargs解决命令行参数过长问题。df看分区整体磁盘;du统计目录树占用大小。- 文件对比:
cmp二进制对比;diff文本差异;patch应用补丁;md5sum做校验;gpg做数字签名验证来源。
重要坑汇总
- shell通配符没有匹配文件时,不会展开,保留原样字符串。
- find默认递归,
‑name模式一定要用引号,防止shell提前展开。- 文件名包含空格、换行,普通管道会解析错乱,GNU工具用
‑print0 / xargs ‑0。- 临时文件放在公共目录
/tmp,要设置umask,防止其他用户篡改。
文章作者 会写代码的小郎中
上次更新 2011-07-23
许可协议 CC BY-NC-ND 4.0