Linux文件字数统计

背景是这样的。最近我在升级维护自己的日记本系统,想要给目录页增加字数统计的功能。

这个系统的工作原理是这样的:先遍历当前文件夹下的所有txt文件,然后根据文件名(就是日记的创建日期)进行排序,并按月份分类,最后根据一个HTML模板生成最终的目录页。这个日记本系统是用Python编写的,但最初为了方便,我在代码中大量使用了Linux的shell指令(例如 ls 获取文件名),因此这次升级时我顺便探索了Linux下统计文件字数的方法。

字数统计初印象: wc指令的用法

学过Linux的人或多或少听说过wc指令,其全称是 word count ,可以用来统计字数和行数。下面是Linux manual page中对这个指令的介绍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
NAME
wc - print newline, word, and byte counts for each
file

SYNOPSIS
wc [OPTION]... [FILE]...
wc [OPTION]... --files0-from=F

DESCRIPTION
Print newline, word, and byte counts for each FILE,
and a total line if more than one FILE is
specified. A word is a nonempty sequence of non
white space delimited by white space characters or
by start or end of input.

With no FILE, or when FILE is -, read standard
input.

The options below may be used to select which
counts are printed, always in the following order:
newline, word, character, byte, maximum line
length.

例如,我们在生信分析pipeline中常常会用到 wc -l <file> 统计文件的行数,也可以使用 wc -c <file> 或者 wc -w <file> 统计文件的bytes数或单词数。但这些并不是我们想要的——日记文件多以utf-8编码的中文字符写成(也会混有数字和英文字母),而 wc -c 统计的是总bytes数,它会把一个中文字符算作3个bytes,导致统计偏差。

那么,该怎么有效统计字符数呢?

-m 参数的妙用,以及LOCALE设置对统计结果的影响

wc 指令的manual page中提到了 -m 指令,其作用是统计字符数量,对我们来说也许有用:

1
2
-m, --chars
print the character counts

比方说,我们统计一下北宋词人柳永的《蝶恋花》:

1
2
3
4
5
6
7
8
9
10
~/sd/termux $ cat test.txt
蝶恋花·伫倚危楼风细细
柳永〔宋代〕
伫倚危楼风细细,望极春愁,黯黯生天际。草色烟光残照里, 无言谁会凭阑意。
拟把疏狂图一醉,对酒当歌,强乐还无味。衣带渐宽终不悔, 为伊消得人憔悴。
~/sd/termux $ wc test.txt
4 4 265 test.txt
~/sd/termux $ wc -m test.txt
92 test.txt
~/sd/termux $

全词算上标题和标点符号共92个字符,但是使用 wc 直接统计,会统计出来265个bytes的大小。相比之下, wc -m 的统计结果是对的。

到此为止,我们本以为故事已经到达了终点,只需要将 wc -m 写入Python文件的字数统计模块,一切就大功告成了

——直到第二天我看到了服务器自动更新以后的字数统计结果——依然不对。

多说一句,这个日记本系统每天会自动扫描并更新一次目录,这个自动扫描的功能托管在Linux的cron服务下。

询问了AI,它告诉我:“crontab环境中,可能缺少正确的locale设置,导致wc命令默认使用C locale,从而将中文字符按字节统计。 解决方法有两种:1. 在crontab中设置正确的locale环境变量。 2. 在Python中使用其他不依赖locale的方法来统计字符数”。为了方便调试,这里我自然选择了方案1。

1
2
3
4
(base) cyclin@vm:~$ LANG=C.UTF-8 wc -m test.txt
92 test.txt
(base) cyclin@vm:~$ LANG=C wc -m test.txt
265 test.txt

如上所示,当我们在调用 wc 指令前强制设置环境变量 LANG ,那么 LANG 的取值会影响 wc -m 的统计结果。在termux环境下测试时大部分LANG设置都能正常统计字数,但是在服务器端(Ubuntu 22.04),只有当设置 LANG=C.UTF-8 时才能让 wc -m 正确识别中文字符。

总结:要想正确统计字符数(并且不依赖于环境变量),那么应该使用这个指令: LANG=C.UTF-8 wc -m <file>


后记:在上述探索之后,DeepSeek建议我如果想要提高性能和安全性,就不要再继续用Linux shell指令进行文件名读取和字数统计了,使用Python内置的文件接口 os.listdirfile.read 是更安全和高效的选择。因此,我又花了一些时间重构了代码——效果还是很显著的,每次扫描的时间从5秒多提升到了不到3秒。

AI可真好用呀。

以及,统计完以后我才发现,这几年下来我的日记居然攒到了一百多万字——原来我在日记里话可真多啊,笑死。