有天早上开会,领导要一份"昨天备份到底存了哪些文件"。我手上有备份脚本吐出的日志,三百一十二行,每行一个完整路径,形如 D:\backup\2024-03-12\site\index.html。粘到共享文档里满屏都是目录前缀,谁也看不出重点。我最初的打算是在记事本里逐行删前半段,删了不到五十行就放弃了——不是删不动,是每行要删的字符数不一样。
斜杠方向不是风格问题,是两套文件系统
Windows 用反斜杠 \ 分隔目录,Unix 和 Linux 用正斜杠 /。同一批数据里两种都可能遇到:日志是 Windows 上的服务写的,附件路径却是从对象存储的接口来的。混在一起手工删就有风险——只按正斜杠找"最后一个分隔符",遇到 C:\Users\name\a.txt 会一个都找不到,把整行当文件名交出去。
我用 文件名提取 的原因很简单:它按行批量处理,反斜杠和正斜杠两种分隔都能识别,三百多行一次贴进去就出结果。省时间的不是少按键,而是不用逐行判断这行属于哪种路径。
还有一点值得记:Linux 里反斜杠是合法的文件名字符,/tmp/a\b.txt 的反斜杠属于名字本身,不是分隔符。我第一次做批量提取时把 Windows 习惯套到一份 Linux 日志上,几个怪名字的文件全被切错。来源混杂的数据,我会先按来源分堆再分别处理。
盘符、UNC 和结尾斜杠这三处最容易翻车
第一处是盘符。C:\Users\name\a.txt 里的冒号出现在 C: 上,它不是分隔符,但手工按冒号或者按斜杠切分的人常在这里多切一刀,得到 \Users\name\a.txt 之类的残段。
第二处是 UNC 路径,网络共享的写法:\\server\share\a.txt。开头两个连续反斜杠不是分隔符重复,它是"这是共享路径"的标记,后面服务器名和共享名各占一段。我手工删的时候把 \\nas01\files\report.pdf 的第一个反斜杠当成转义删掉了,那行粘进批处理脚本后再也不是合法路径。
第三处是结尾带斜杠的路径,比如 /home/deploy/app/。这是一个目录,斜杠后面是空的。按"取最后一段"的规则,结果是空字符串而不是 app。我现在的检查动作是把结果列和输入行数对一下,看有没有空行冒出来——有空行就说明原数据里有目录形态的路径,那一类要么单独处理,要么明确"目录名不算文件名"。
要不要扩展名,先问听众
这是提取动作里第二个要决定的事。给运维看,带扩展名才有用,access.log 和 access 是两个东西;给领导做汇报清单,多数时候只要名字,一列 .pdf、.zip 反而干扰阅读。工具这边可以选是否包含扩展名,所以我一般两版都跑,自己留带扩展名的那份,交出去用不带的。
去掉扩展名靠的是最后一个点。例外是 pack.tar.gz:按最后一个点切得到 pack.tar 和 gz。我那份备份清单里就有这种归档文件,不带扩展名那版留着 .tar,一眼看过去像命名不规则。要按 .tar.gz、.tar.bz2 这种多段扩展名正确分离,得用 文件名后缀拆分,别把普通"去后缀"的结果当成错。
反过来还有没扩展名的文件。Linux 下 /opt/bin/nginx、/etc/passwd 都没有点,这种行开着"不含扩展名"时我就特别小心,规则不明确会削掉真实名字的一部分,我会把它们单独挑出来对一遍。
去重改变的是条数,不是格式
三百一十二行里,勾上去重后只剩两百零五个名字。差出来的一百零七个不是错误,是同名文件:index.html 在七个站点目录里各有一份,app.log 因为一天滚动多次出现了十几行。提取只留下最后一个分隔符之后的部分,前面的目录信息是区分它们的唯一线索,去重之后归属就查不回去了。
所以我把"要不要去重"变成一句要回答的问题:要的是文件种类数,还是条目数。种类数就去重;条目数就不去重,因为备份是不是每个都做了,看的是三百一十二这个数。两者都要,我在去重前先留一份带路径的原始结果。
| 输入路径(原样) | 提取到的文件名 | 不含扩展名 | 要点 |
|---|---|---|---|
C:\Users\name\Documents\a.txt | a.txt | a | 盘符里的冒号不是分隔符 |
\\server\share\backup\report.pdf | report.pdf | report | 开头两个反斜杠是共享标记 |
/var/log/nginx/access.log | access.log | access | Unix 下反斜杠可属于文件名 |
D:\data\archive\pack.tar.gz | pack.tar.gz | pack.tar | 按最后一个点分,多段扩展名要另处理 |
/home/deploy/app/ | 空(最后是斜杠) | 空 | 这是目录,名字在倒数第二段 |
/opt/bin/nginx | nginx | nginx | 本身无扩展名,别多削一刀 |
file:///data/report.pdf | report.pdf | report | 协议前缀会被当成目录段 |
最后一行那种 file:/// 前缀是我另一份数据里遇到的,会被当成一段目录处理,文件名本身取对了;但如果我要的是去掉前缀保留相对路径,那就不是提取的活儿——我会先用 文本行操作 删掉每行左侧固定数量的字符再提取,顺序不能反。前缀长度不固定时这种按位置的做法会失效,得改成替换。
几个我当时踩过的疑问
Q:路径里带空格或者中文,会不会切断?
不会。分隔符只有斜杠那两种,空格和中文都属于名字的一部分,C:\我的文件\年报 2024.docx 提取出来是完整的 年报 2024.docx。真要出问题的是粘进脚本时没给路径加引号,那是引用语法的问题。
Q:日志里一行有几个路径怎么办?
批量提取按行来。我遇到一行里两个路径的日志,先拆成一行一个再提,不拆的话取到的分隔符位置来自最后一个路径,前面那一整个会被削掉。
Q:目录名和文件名混在一起,能只提文件吗?
我不指望自动区分——结尾是斜杠的那一行是我的判断依据,我会把那些行单独摘出来当目录处理,剩下的按文件提。混着交出去最容易出空结果。
Q:提取后顺序会变吗?
输出与输入按行一一对应,所以我能拿两边行数直接比。核对时我只看两个数:行数是否相等、结果里空行数是否为零。
Q:几百行会不会一次处理不过来?
我那批三百一十二行是一次贴进去的,没分批,核对很直观——行数相等就通过。真要上万行我更担心的不是处理而是核对,那种量级我会先按目录分组、一段一段提,免得对不上时无从回溯。
回到那份备份清单。最后交出去的是两百零五个不带扩展名的名字,我在旁边标了一行"原始记录 312 条,同名文件已合并",领导要的"存了哪些"和运维要的"每个都存了"这两个口径就分开了。要不要去重、要不要扩展名、结尾有斜杠的行怎么处理——这三个决定比"用哪个工具"更影响结果。