如何在 Linux 中将文件批量重命名为数字文件名

想在 Linux 中将一整套文件重命名为数字序列(1.pdf、2.pdf、3.pdf ……)?这可以通过一些简单的脚本来完成,本文将向您展示如何做到这一点。
数字文件名
通常当我们使用某些硬件(手机、专用 PDF 扫描仪)扫描 PDF 文件时,文件名会显示为类似 2020_11_28_13_43_00.pdf 的内容。许多其他半自动系统生成类似的基于日期和时间的文件名。
有时,该文件还可能包含正在使用的应用程序的名称,或一些其他信息,例如适用的 DPI(每英寸点数)或扫描的纸张尺寸。
当从不同来源一起收集 PDF 文件时,文件命名约定可能会有很大差异,最好使用数字(或部分数字)文件名进行标准化。
这也适用于其他域和文件集。例如,您的食谱或照片集、自动监控系统生成的数据样本、准备存档的日志文件、数据库工程师的一组 SQL 文件,以及通常从具有不同命名方案的不同来源收集的任何数据。
将文件批量重命名为数字文件名
在 Linux 中,很容易将一整套文件名完全不同的文件快速重命名为一个数字序列。 “简单”在这里意味着“易于执行”:将文件批量重命名为数字的问题本身编码起来很复杂:下面的 oneliner 脚本花了 3-4 个小时来研究、创建和测试。尝试过的许多其他命令都有我想避免的限制。
请注意,我们不提供任何保证,并且此代码按“原样”提供。请在运行之前进行自己的研究。也就是说,我确实针对具有各种特殊字符的文件成功地测试了它,并且还针对超过 50k 的文件进行了测试,没有任何文件丢失。我还检查了一个名为 a$na.pdf
的文件,其中包含换行符。
if [ ! -r _e -a ! -r _c ]; then echo 'pdf' > _e; echo 1 > _c ;find . -name "*.$(cat _e)" -print0 | xargs -0 -I{} bash -c 'mv -n "{}" $(cat _c).$(cat _e);echo $[ $(cat _c) + 1 ] > _c'; rm -f _e _c; fi
我们先看看它是如何工作的,然后再分析命令。我们创建了一个包含八个文件的目录,所有文件的名称都非常不同,除了它们的扩展名匹配并且是 .pdf。我们接下来运行上面的命令:

结果就是这8个文件改名为1.pdf、2.pdf、3.pdf等,虽然之前的名字已经偏移了很多。
该命令假定您还没有任何 1.pdf 到 x.pdf 命名文件。如果这样做,您可以将这些文件移动到一个单独的目录中,将 echo 1
设置为更高的数字以开始重命名给定偏移量处的剩余文件,然后再次将两个目录合并在一起。
请始终注意不要覆盖任何文件,在更新任何内容之前进行快速备份始终是个好主意。
让我们详细看看这个命令。通过将 -t
选项添加到 xargs
可以帮助我们了解发生了什么,这让我们可以看到幕后发生了什么:

首先,该命令使用两个小的临时文件(名为 _e 和 _c)作为临时存储。在 oneliner 的开始,它使用 if
语句进行安全检查,以确保 _e 和 _c文件不存在。如果存在具有该名称的文件,脚本将不会继续。
关于使用小临时文件与变量的主题,我可以说虽然使用变量是理想的(节省一些磁盘 I/O),但我遇到了两个问题。
第一个是,如果您在 oneliner 的开头导出一个变量,然后稍后使用该相同的变量,如果另一个脚本使用相同的变量(包括该脚本在同一台机器上同时运行多次),那么该脚本,或者这个,可能会受到影响。在重命名许多文件时最好避免这种干扰!
第二个是 xargs 与 bash -c 的组合似乎在 bash -c
命令行中的变量处理方面有限制。即使是广泛的在线研究也没有为此提供可行的解决方案。因此,我最终使用了一个小文件 _c 来保持进度。
_e 是我们将要搜索和使用的扩展,_c 是一个计数器,每次重命名时都会自动增加。 echo $[ $ (cat _c) + 1 ] > _c
代码通过显示带有 cat
的文件,添加一个数字,然后重新写它。
该命令还通过使用空终止而不是标准的换行终止,即 字符,使用了处理特殊文件名字符的最佳方法。这是由
find
的 -print0
选项和 xargs 的 -0
选项确保的。
find 命令将搜索具有 _e 文件中指定扩展名的任何文件(由 echo pdf > _e
命令创建。您可以将此扩展名更改为任何其他扩展名你想要,但请不要在它前面加上点。点已经包含在后面的 *.$ (cat _e)
-name
说明符中 找到
。
一旦 find 找到所有文件并将它们发送到 终止到 xargs,xargs 将使用计数器文件 (< i>_c) 和相同的扩展文件 (_e)。要获取这两个文件的内容,可以使用一个简单的
cat
命令,该命令从子 shell 中执行。
mv
移动命令使用 -n
来避免覆盖任何已经存在的文件。最后,我们通过删除它们来清理这两个临时文件。
虽然使用两个状态文件和子 shell 分叉的成本可能有限,但这确实会增加脚本的一些开销,尤其是在处理大量文件时。
对于同一问题,网上有各种各样的其他解决方案,许多人已经尝试创建一个完全有效的解决方案,但都以失败告终。许多解决方案忘记了各种附带情况,例如使用 ls
而不指定 --color=never
,这可能导致在目录列表颜色编码为用过的。
然而,其他解决方案无法正确处理带有空格、换行符和特殊字符(如“)的文件。为此,组合 find ... -print0 ... | xargs -0 ...
通常是指示和理想的(find 和 xargs 手册都非常强烈地提到了这个事实)。
虽然我不认为我的实现是完美的或最终的解决方案,但它似乎通过使用 find
和 终止字符串对许多其他解决方案做出了重大改进,确保最大文件名和解析兼容性,以及一些其他细节,例如能够指定起始偏移量,以及完全Bash-native 。
尽情享受吧!