>>> from env_helper import info; info()
页面更新时间: 2023-06-30 19:19:17
运行环境:
Linux发行版本: Debian GNU/Linux 12 (bookworm)
操作系统内核: Linux-6.1.0-9-amd64-x86_64-with-glibc2.36
Python版本: 3.11.2
3.6. 项目:将带有美国风格日期的文件改名为欧洲风格日期¶
假定你的老板用电子邮件发给你上千个文件,文件名包含美国风格的日期 (MM-DD-YYYY),需要将它们改名为欧洲风格的曰期(DD-MM-YYYY)。 手工完成这个无聊的任务可能需要几天时间!让我们写一个程序来完成它。
下面是程序要做的事:
检查当前工作目录的所有文件名,寻找美国风格的日期。
如果找到,将该文件改名,交换月份和日期的位置,使之成为欧洲风格。这意味着代码需要做下面的事情:
创建一个正则表达式,可以识别美国风格日期的文本模式。
调用
os.listdir()
,找出工作目录中的所有文件。循环遍历每个文件名,利用该正则表达式检查它是否包含曰期。
如果它包含日期,用
shutil.move()
对该文件改名。
对于这个项目,打开一个新的文件编辑器窗口, 将代码保存为
renameDates.py
。
3.6.1. 第1步:为美国风格的日期创建一个正则表达式¶
程序的第一部分需要导入必要的模块,并创建一个正则表达式, 它能识别
MM-DD-YYYY 格式的日期。TODO
注释将提醒你,
这个程序还要写什么。将它们作为 TODO
,就很容易利用 IDLE的 Ctrl-F
查找功能找到它们。让你的代码看起来像这样:
#! python3
# renameDates.py - Renames filenames with American MM-DD-YYYY date format
# to European DD-MM-YYYY.
import shutil, os, re
# Create a regex that matches files with the American date format.
datePattern = re.compile(r"""^(.*?) # all text before the date
((0|1)?\d)- # one or two digits for the month
((0|1|2|3)?\d)- # one or two digits for the day
((19|20)\d\d) # four digits for the year
(.*?)$ # all text after the date
""", re.VERBOSE)
# TODO: Loop over the files in the working directory.
# TODO: Skip files without a date.
# TODO: Get the different parts of the filename.
# TODO: Form the European-style filename.
# TODO: Get the full, absolute file paths.
# TODO: Rename the files.
通过本章,你知道 shutil.move()
函数可以用于文件改名:
它的参数是要改名的文件名,以及新的文件名。 因为这个函数存在于 shutil
模块中, 所以你必须导入该模块。
在为这些文件改名之前,需要确定哪些文件要改名。 文件名如果包含
spam4-4-1984.txt 和01-03-2014eggs.zip 这样的日期,
就应该改名,而文件名不包含日期的应该忽略, 诸如 littlebrother.epub
。
可以用正则表达式来识别该模式。在开始导入 re
模块后,调用
rexompile()
创建 一个 Regex
对象。 传入 re.VERBOSE
作为第二参数, 这将在正则表达式字符串中允许空白字符和注释, 让它更可读。
正则表达式字符串以 ^(.*?)
开始,
匹配文件名开始处、日期出现之前的任何文本。 ((0|l)?\d)
分组匹配月份。第一个数字可以是0或1,
所以正则表达式匹配12,作为十二月份,也会匹配02,
作为二月份。这个数字也是可选的, 所以四月份可以是04或4。 日期的分组是
((0|l|2|3)?\d)
, 它遵循类似的逻辑。3、03和31是有效的日期数字
(是的,这个正则表达式会接受一些无效的日期,
诸如4-31-2014、2-29-2013和0-15-2014。
日期有许多特例,很容易被遗漏。为了简单,
这个程序中的正则表达式已经足够好了)。
虽然1885是一个有效的年份,但你可能只在寻找20世纪和21世纪的年份。
这防止了程序不小心匹配非日期的文件名,它们和日期格式类似,诸如
10-10-1000.txt
。
正则表达式的 (.*?)$
部分,将匹配日期之后的任何文本。
3.6.2. 第2步:识别文件名中的日期部分¶
接下来,程序将循环遍历 os.listdir()
返回的文件
名字符串列表,用这个正则表达式匹配它们。
文件名不包含日期的文件将被忽略。如果文件名包含日期,
匹配的文本将保存在几个变量中。 用下面的代码代替程序中前3个 TODO
:
#! python3
# renameDates.py - Renames filenames with American MM-DD-YYYY date format
# to European DD-MM-YYYY.
--snip--
# Loop over the files in the working directory.
for amerFilename in os.listdir('.'):
mo = datePattern.search(amerFilename)
# Skip files without a date.
if mo == None:
continue
# Get the different parts of the filename.
beforePart = mo.group(1)
monthPart = mo.group(2)
dayPart = mo.group(4)
yearPart = mo.group(6)
afterPart = mo.group(8)
--snip--
如果 search()
方法返回的 Match
对象是 None
, 那么
amerFilename
中的文件名 不匹配该正则表达式。 continue
语句将跳过
循环剩下的部分,转向下一个文件名。
否则,该正则表达式分组匹配的不同字符串, 将保存在名为 beforePart
、
monthPart
、 dayPart
、 yearPart
和 afterPar
的变量中。这些变量中的字符串
将在下一步中使用,用于构成欧洲风格的文件名。
为了让分组编号直观,请尝试从头阅读该正则表达式, 每遇到一个左括号就计数加一。不要考虑代码, 只是写下该正则表达式的框架。 这有助于使分组变得直观,例如:
datePattern = re.compile(r"""^(1) # all text before the date
(2 (3) )- # one or two digits for the month
(4 (5) )- # one or two digits for the day
(6 (7) ) # four digits for the year
(8)$ # all text after the date
""", re.VERBOSE)
这里,编号1至8代表了该正则表达式中的分组。 写出该正则表达式的框架, 其中只包含括号和分组编号。 这让你更清楚地理解所写的正则表达式, 然后再转向程序中剩下的部分。
3.6.3. 第3步:构成新文件名,并对文件改名¶
作为最后一步,连接前一步生成的变量中的字符串,
得到欧洲风格的日期:曰期在月份之前。 用下面的代码代替程序中最后3个
TODO
:
>>> #! python3
>>> # renameDates.py - Renames filenames with American MM-DD-YYYY date format
>>> # to European DD-MM-YYYY.
>>>
>>> import shutil, os, re
>>>
>>> # Create a regex that matches files with the American date format.
>>> datePattern = re.compile(r"""^(.*?) # all text before the date
>>> ((0|1)?\d)- # one or two digits for the month
>>> ((0|1|2|3)?\d)- # one or two digits for the day
>>> ((19|20)\d\d) # four digits for the year
>>> (.*?)$ # all text after the date
>>> """, re.VERBOSE)
>>>
>>>
>>> for amerFilename in os.listdir('.'):
>>> mo = datePattern.search(amerFilename)
>>> print(mo)
>>>
>>> # Skip files without a date.
>>> if mo == None:
>>> continue
>>> else:
>>> # Get the different parts of the filename.
>>> beforePart = mo.group(1)
>>> monthPart = mo.group(2)
>>> dayPart = mo.group(4)
>>> yearPart = mo.group(6)
>>> afterPart = mo.group(8)
>>>
>>> # Form the European-style filename.
>>> euroFilename = beforePart + dayPart + '-' + monthPart + '-' + yearPart + afterPart
>>>
>>> # Get the full, absolute file paths.
>>> absWorkingDir = os.path.abspath('.')
>>> amerFilename = os.path.join(absWorkingDir, amerFilename)
>>> euroFilename = os.path.join(absWorkingDir, euroFilename)
>>>
>>> # Rename the files.
>>> print('Renaming "%s" to "%s"...' % (amerFilename, euroFilename))
>>> shutil.move(amerFilename, euroFilename) # uncomment after testing
None
None
None
None
None
None
None
None
None
None
None
None
None
None
None
None
None
将连接的字符串保存在名为 euroHlename
的变量中。 然后将
amerFilename
中原来的文件名和新 的 euroFilename
变量传递给
shutil.move()
函数, 将该文件改名。
这个程序将 shutil.move()
调用注释掉, 代之以打印出将被改名的文件名。
先像这样运行程序,你可以确认文件改名是正确的。 然后取消
shutil.move()
调用的注释, 再次运行该程序,确实将这些文件改名。
3.6.4. 第4步:类似程序的想法¶
有很多其他的理由,导致你需要对大量的文件改名。
为文件名添加前缀,诸如添加
spam_
,将eggs.txt
改名为spam_eggs.txt
。将欧洲风格日期的文件改名为美国风格日期。
删除文件名中的0,诸如
spam0042.txt
。