假定老板用电子邮件发了上千个文件,文件名包含美国风格的日期 (MM-DD-YYYY),需要将它们改名为欧洲风格的曰期(DD-MM-YYYY)。 手工完成这个无聊的任务可能需要几天时间!那么接下来写一个程序来完成它。
下面是程序要做的事:
- 检查当前工作目录的所有文件名,寻找美国风格的日期。
- 如果找到,将该文件改名,交换月份和日期的位置,使之成为欧洲风格。这意味着代码需要做下面的事情:
- 创建一个正则表达式,可以识别美国风格日期的文本模式。
- 调用
os.listdir()
,找出工作目录中的所有文件。 - 循环遍历每个文件名,利用该正则表达式检查它是否包含曰期。
- 如果它包含日期,用
shutil.move()
对该文件改名。
对于这个项目,打开一个新的文件编辑器窗口,
将代码保存为 renameDates.py
。
为美国风格的日期创建一个正则表达式
程序的第一部分需要导入必要的模块,并创建一个正则表达式,
它能识别 MM-DD-YYYY 格式的日期。TODO
注释将会提醒,
这个程序还要写什么。将它们作为 TODO
,就很容易利用
IDLE的 Ctrl-F 查找功能找到它们。让代码看起来像这样:
renameDates.py
- 将文件名中的美国日期格式(MM-DD-YYYY) 重命名为欧洲日期格式(DD-MM-YYYY)。
#! python3
import shutil, os, re
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
。
正则表达式的 (.*?)$
部分,将匹配日期之后的任何文本。
识别文件名中的日期部分
接下来,程序将循环遍历 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)
renameDates.py
- 使用美国MM-DD-YYYY日期格式重命名文件名至欧洲DD-MM-YYYY。
#! python3
import shutil, os, re
创建一个正则表达式,将文件与美国日期格式相匹配。
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 None 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()
调用的注释,
再次运行该程序,确实将这些文件改名。
其他程序设计思路
有很多其他的理由,导致需要对大量的文件改名。
- 为文件名添加前缀,诸如添加
spam_
,将eggs.txt
改名为spam_eggs.txt
。 - 将欧洲风格日期的文件改名为美国风格日期。
- 删除文件名中的0,诸如
spam0042.txt
。