5.2 目录与文件操作


5.2 目录与文件操作

目录遍历靠 glob 或 opendir/readdir,文件甄别靠一元测试运算符-f 是普通文件、-M 是年龄(天)。两者组合就能回答"这个目录下最近一天修改过的 .log 文件有哪些"。

两种遍历方式

# 方式一:glob,模式匹配的快捷门 for my $f (glob '/var/log/app/*.log') { print "$f\n"; } # 方式二:opendir/readdir,能看见隐藏文件,拼路径更明确 opendir my $dh, '/var/log/app' or die "打不开目录: $!"; while (my $name = readdir $dh) { next if $name eq '.' or $name eq '..'; my $path = "/var/log/app/$name"; next unless -f $path; print "$path\n"; } closedir $dh;

日常取文件 glob 够用;要区分文件与子目录、或要递归时,opendir 的控制力更强。递归下钻推荐直接用 CPAN 的 File::Find

use File::Find; find(sub { return unless /\.log$/; print "$File::Find::name\n"; }, '/var/log/app');

文件测试运算符速查

运算符 问的问题 日志场景
-e 存在吗 处理前确认文件就位
-f / -d 文件 / 目录 遍历时分流
-M 距修改多少天 只处理今天的日志
-s 多少字节 跳过空文件
-r / -w 可读 / 可写 动手前验权限
for my $f (glob "$dir/*.log") { next unless -s $f; # 空文件跳过 next if -M $f > 1; # 只碰一天内的 process($f); }

创建、改名、删除

mkdir $outdir or die "建目录失败: $!"; # 单层 make_path $deep_dir if !-d $deep_dir; # 多层,用 File::Path rename $tmp_report, $report or die "改名失败: $!"; # 原子上桌 unlink @stale_tmp; # 删一批临时文件

rename 在同一文件系统上通常是原子操作——这个性质是 5.3 节"临时文件 + 改名"惯例的基石:读报告的一方永远不会看到写了一半的文件。

💡 关键直觉:把"找文件"和"处理文件"分成两步写。先收集全部路径进数组,再统一处理——报错时能打印"第 17 个文件失败",比边找边处理的可排查性好一个量级。

实战:一份"今日日志体检单"

把遍历、测试、统计拼成一段可直接抄走的体检代码,输出目录下每个日志的年龄与大小:

use File::Find; my ($n, $bytes, @stale) = (0, 0); find(sub { return unless -f $_ && /\.log(?:\.gz)?$/; $n++; $bytes += -s $_; push @stale, $File::Find::name if -M _ > 7; # _ 复用上次的 stat 缓存 }, $dir); printf "%d 个日志,共 %.1f MB\n", $n, $bytes / 1048576; printf "超过 7 天未动的有 %d 个:\n", scalar @stale; print " $_\n" for @stale;

第 4 行的 -M _ 有个实用细节:下划线表示"沿用上一次 stat 的结果",避免 -f-s-M 各查一次文件系统——大目录里这一下能省出可观的系统调用。这份体检单放进 cron 每天跑一次,日志堆积问题在变成事故前就能被看见。

权限与属主的三个注意点

批量操作文件还常撞上权限现实:一是 unlink 失败不置脚本于死地,它返回成功删除的个数,unlink @files or warn "有文件没删掉" 的写法比 die 合适,删不掉的旧归档不该阻断流水线;二是 chmod 要给新产出的报告文件收紧权限(含敏感数据时 chmod 0600, $report),默认权限往往偏宽;三是跨文件系统 rename 不再原子,会退化为"复制 + 删除",大文件上耗时且中途可见——重要报告产出目录应与临时目录同盘。

一次真实的目录事故复盘

把本章知识串进一个真实形态的故障。现象:日志清理脚本每周日跑,某次执行后监控目录空了三小时。复盘时间线:脚本用 glob 拿文件列表,当夜日志轮转正在进行,glob 结果里混进了一个刚建出的临时目录(匹配了宽松模式);unlink 对目录无效并静默失败;但脚本随后按"清理失败则移动到回收目录"的逻辑执行 rename,把半成品目录搬走了,轮转进程找不到自己的临时目录报错退出,直到下轮重试才恢复。三层修复各自对应一条本章原则:glob 模式收紧并加 -f 过滤(只碰确认过的普通文件);rename 前同样测试(-f $src or next);清理脚本与轮转任务错峰(cron 分钟位错开)。教训浓缩成一句:批量操作对"列表里是什么"的信任必须为零——glob 给的是"匹配结果",不是"你要的东西",两者之间的差距就是事故的生存空间。把这个原则再推广半步就覆盖了日常:任何"拿一个列表去删/移/改"的代码,都值得在动作前打印列表人工确认一次(dry-run),确认无误再放开执行——五行 dry-run 代码,是批处理脚本最便宜的保险。dry-run 的实现形态通常是一个 --dry 命令行开关:有它就只打印"将要对哪些文件做什么",没有它才真动手,一行参数解析换一个"看清再动手"的机会,这笔账怎么算都划算。

本节要点回顾

  • glob 快捷、opendir 可控,递归交给 File::Find
  • readdir 返回名字不带路径,拼接时别忘了目录前缀
  • -M-s-f 是日志脚本的三件常用测试
  • rename 同盘原子,是安全产出文件的标准收尾
  • 先收集路径再处理,失败可定位到具体文件

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U