https://www.cnblogs.com/clover-toeic/p/5235316.html
中國知網文件亂碼修復方法。
Linux->Windows主機目錄和文件名中文亂碼恢復
標籤: 字符編碼 Python
[TOC]
聲明
本文主要記述作者如何通過Python腳本恢復跨平臺傳輸導致的目錄和文件名中文亂碼。作者對Python編程和字符編碼瞭解不多,紕漏難免,歡迎指正。同時,本文兼做學習筆記,存在囉嗦之處,敬請諒解。
本文同時也發佈於作業部落,閱讀體驗可能更好。
一. 亂碼問題
一年前,作者將Windows XP系統主機下創建的一批文件(以多級目錄組織),通過Samba
手工拷貝至Linux系統主機,能正常顯示目錄和文件名中包含的中文字符。然後,通過filezilla
連接Linux主機,將上述文件下載至移動硬盤,這個過程中也未出現亂碼。但將移動硬盤連接到Windows
7系統主機上時,卻發現目錄和文件名中包含的中文字符出現亂碼。
例如,文件名"GNU Readline庫函數的應用示例"和"守護進程接收終端輸入的一種變通性方法"分別顯示爲"GNU Readlineåºå½æ°çåºç¨ç¤ºä¾"和"å®æ¤è¿ç¨æ¥æ¶ç»ç«¯è¾å ¥çä¸ç§åéæ§æ¹æ³"。
但除目錄和文件名出現中文亂碼外,文件內容並無亂碼。
作者當時並不熟悉字符編碼知識,於是請教《通俗易懂地解決中文亂碼問題(1) --- 跨平臺亂碼》一文的作者Roly-Poly。Roly-Poly非常熱心地轉換了上述兩個文件名,並給出效果圖:
以及相應的Java轉換方法:
String str = new String(new String(messyName.getBytes("ISO-8859-1"), "GBK").getBytes("GBK"), "UTF-8");
其中,messyName對應出現亂碼的字符串。getBytes(charset)
將Unicode編碼存儲的字符串按照charset編碼,並以字節數組表示;new
String(bytes[], charset)
則將字節數組按照charset編碼進行組合識別,最後轉換爲Unicode存儲。因此,上述代碼表示先將當前編碼從 ISO-8859-1轉爲GBK,然後再從GBK轉爲UTF-8。
當然,Roly-Poly的轉換仍有缺憾,畢竟還存在未能正確解析的亂碼。"幸運"的是,當時出於謹慎,作者分別通過dir
/S path
、tree
/F path
(Windows)和ls
-lRS --time-style=long-iso
(Linux)創建了三份文件列表。這樣,在Roly-Poly轉碼的基礎上再做些校驗,有望替換爲完全正確的文件名。
然而,一方面因爲作者對Java語言和字符編碼比較陌生(主要是懶),另一方面因爲解析文件列表並更名的工作量預期較大,作者一直未付諸實踐。直到最近,纔開始從頭着手處理亂碼問題。這一過程學到不少知識,也走過不少彎路。教訓就是:凡事要一鼓作氣!
二. 調試環境
作者使用Python
2.7
自帶的IDLE
進行編碼調試。除非特別說明,本文所有代碼均爲Python語言。
參考Python字符編碼詳解一文,獲取當前環境的默認編碼:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys, locale
def SysCoding():
fmt = '{0}:{1}'
#當前系統所使用的默認字符編碼
print fmt.format('DefaultEncoding ', sys.getdefaultencoding())
#轉換Unicode文件名至系統文件名時所用的編碼('None'表示使用系統默認編碼)
print fmt.format('FileSystemEncoding ', sys.getfilesystemencoding())
#默認的區域設置並返回元祖(語言, 編碼)
print fmt.format('DefaultLocale ', locale.getdefaultlocale())
#用戶首選的文本數據編碼(猜測結果)
print fmt.format('PreferredEncoding ', locale.getpreferredencoding())
if __name__ == '__main__':
SysCoding()
作者的Windows XP系統主機上,區域和語言選項
->區域選項
->標準和格式
及高級
->非Unicode程序的語言
均設置爲"中文(中國)";Windows
7系統主機上,區域和語言
->格式
及管理
->非Unicode程序的語言
均設置爲"中文(簡體,中國)"。兩臺主機的SysCoding()
輸出相同,均顯示如下:
DefaultEncoding :ascii
FileSystemEncoding :mbcs
DefaultLocale :('zh_CN', 'cp936')
PreferredEncoding :cp936
三. 目錄和文件名亂碼恢復
3.1 可選方案
3.1.1 通過合適的編解碼轉換
可使用chardet
模塊detect()
函數檢測給定字符的編碼。該函數返回檢測到的編碼'encoding'
及其可信度'confidence'
。
安裝方法爲:命令提示符下執行C:\Python27\Scripts>easy_install.exe
chardet
後,自動下載egg文件包。若未安裝成功(import提示"ImportError: No module named chardet"),可到C:\Python27\Lib\site-packages目錄解壓egg文件包,將其中的chardet
目錄(所有文件)拷貝到site-packages下面即可。
安裝成功後,按照以下方法檢測字符編碼:
#coding: gbk
import chardet
print chardet.detect('abc') #{'confidence': 1.0, 'encoding': 'ascii'}
print chardet.detect('喊') #{'confidence': 0.73, 'encoding': 'windows-1252'}
print chardet.detect('漢') #{'confidence': 0.99, 'encoding': 'TIS-620'} ##Thailand
print chardet.detect('漢中華人民共和國') #{'confidence': 0.99, 'encoding': 'GB2312'}
print '漢中華人民共和國', repr('漢中華人民共和國')
#漢中華人民共和國 '\xba\xba\xd6\xd0\xbb\xaa\xc8\xcb\xc3\xf1\xb9\xb2\xba\xcd\xb9\xfa'
可見,當字符"樣本"過少時,chardet
檢測結果並不準確(如'漢'被識別爲泰文)。在Shell中執行上述檢測時,結果與之相同。
作爲對比,聲明爲coding:
utf-8
時,檢測結果又是另一番"景象":
import chardet
print chardet.detect('abc') #{'confidence': 1.0, 'encoding': 'ascii'}
print chardet.detect('æCUnitè¿è¡') #{'confidence': 0.99, 'encoding': 'utf-8'}
print chardet.detect('喊') #{'confidence': 0.73, 'encoding': 'windows-1252'}
print chardet.detect('漢') #{'confidence': 0.73, 'encoding': 'windows-1252'}
print chardet.detect('漢中華人民共和國') #{'confidence': 0.99, 'encoding': 'utf-8'}
print '漢中華人民共和國', repr('漢中華人民共和國')
#奼変腑鍗庝漢姘戝叡鍜屽浗 '\xe6\xb1\x89\xe4\xb8\xad\xe5\x8d\x8e\xe4\xba\xba\xe6\xb0\x91\xe5\x85\xb1\xe5\x92\x8c\xe5\x9b\xbd'
可見,'å£è¯å¥æ_æ°æµ'
被檢測爲UTF-8編碼。這是因爲UTF-8是ASCII的超集。當字符串序列中所有字符均爲ASCII符號(前128個字符)時,chardet
認爲該串爲ASCII編碼;當字符串序列中也含有所有擴展ASCII符號時,chardet
很可能認爲該串爲UTF-8編碼。此外,print
根據本地操作系統默認字符編碼(GBK),將'漢中華人民共和國'
打印爲奼変腑鍗庝漢姘戝叡鍜屽浗
。
3.1.2 根據文件列表信息匹配
通過正則表達式提取文件列表中的目錄大小、文件數目、文件名及其大小、創建時間等信息,再遍歷移動硬盤亂碼目錄,進行匹配和更名。爲保險起見,應維護一份映射文件,存儲文件路徑、原名和新名,以便恢復或校正。
爲減少匹配和更名次數,只操作名稱包含字母數字以外字符的目錄和文件。此外,還可對文件列表排序,如dir
path /S /O:S
(按大小升序排列)。
3.1.3 機器學習
因爲word、網頁等文件打開後通常可以看到標題,作者得以整理若干文件亂碼名與正常名的映射數據。這樣,藉助機器學習(如基於實例的算法),最終有望消除所有亂碼。
顯然,這一方案難度太高,並不現實。
3.2 逐步實踐
3.2.1 獲取單級目錄及其文件信息
首先,創建名稱正常的多級目錄供調試用。之所以不用原始亂碼目錄調試,是因爲一旦測試失敗很可能會破壞"樣本"。
然後,通過以下代碼獲取單級目錄的大小、文件數目、文件名及大小、創建時間等信息:
import time
from os.path import join, getsize, getmtime, getctime
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
#os.getcwd()返回當前工作目錄
def FilesInfo():
for root, dirs, files in os.walk(CURRENT_DIR):
for file in files:
path = join(root, file)
ctime = time.ctime(getctime(path)) #創建時間
print 'Name:%-12s Size:%-7s Ctime:%s' %(file, getsize(path), ctime)
print root, "consumes",
print sum(getsize(join(root, file)) for file in files),
print "bytes in", len(files), "non-directory files!"
注意,當本模塊由其他模塊import並執行時,os.getcwd()
返回的並非本模塊目錄。
執行FilesInfo()
後,輸出結果如下:
Name:Coding.py Size:7936 Ctime:Mon Feb 29 09:41:15 2016
Name:d_res.bmp Size:522534 Ctime:Mon Feb 29 09:41:15 2016
Name:error3.bmp Size:70782 Ctime:Mon Feb 29 09:41:15 2016
Name:Open.bmp Size:354746 Ctime:Mon Feb 29 09:41:15 2016
Name:Thumbs.db Size:19968 Ctime:Mon Feb 29 09:41:47 2016
Name:typec.bmp Size:199022 Ctime:Mon Feb 29 09:41:15 2016
Name:WalkDir.py Size:4894 Ctime:Mon Feb 29 09:41:15 2016
Name:復Coding.py Size:6564 Ctime:Mon Feb 29 15:41:37 2016
E:\PyTest\stuff consumes 1186446 bytes in 8 non-directory files!
3.2.2 從文件列表中提取目錄和文件信息
在命令提示符下dir
\F
出調試目錄的結構。截取部分如下:
C:\Program Files\IDM Computer Solutions\UEStudio>e:
E:\PyTest 的目錄
2016-02-24 11:53 <DIR> .
2016-02-24 11:53 <DIR> ..
2016-02-23 17:22 1,434 backup_ver2.py
然後,通過ParseFileList()
函數解析出"E:\PyTest"
之類的路徑:
import codecs, re
def ParseFileList():
#Windows記事本默認的字符編碼爲"ANSI"(實際是GBK)
file = codecs.open(r'E:\PyTest\filelist.txt', encoding='gbk')
for line in file:
#下句等效於m = re.match(u' (.+) 的目錄\s*$', line)
m = re.compile(u""" # Python默認字符編碼爲Ansi, 需加u轉爲Unicode
(.+) 的目錄 # 將' 的目錄'前的部分作爲分組(Group)
\s*$ # 行尾""", re.X).match(line)
if m != None:
print m.groups()[0]
注意,此處並未使用內置的open()
方法打開文件。因爲該方法得到的line
爲str類型,需要使用正確的編碼格式進行decode(),否則將無法匹配到"的目錄"
。而codecs.open()
方法打開文件時讀取的就是Unicode類型,不容易出現編碼問題。
當然,若將源代碼文件中的字符編碼聲明改爲#coding=gbk
,並去掉pattern字符串前綴u
,則使用內置的open()
方法仍可匹配到"的目錄"
。
目錄大小、文件數目、文件名及其大小等,均可通過合適的正則表達式提取。然而,作者很快意識到,根據文件列表遍歷和更名的方案實現起來過於複雜。於是,放棄正則匹配的嘗試。
3.2.3 遍歷目錄並更名
雖然文件列表正則匹配的方案不可行,但遍歷和更名卻是所有方案所必需的。
Python中有三種遍歷目錄的方法,即os.listdir()
、os.walk()
和os.path.walk()
。這三者中,作者首選os.walk()
方法,遍歷代碼如下:
import os
def ValidateDir(dirPath):
#判斷路徑是否爲Unicode。若否,將其轉換爲Unicode編碼
if isinstance(dirPath, unicode) == False:
#下句等效於dirPath = dirPath.decode('utf8')
dirPath = unicode(dirPath, 'utf8')
#判斷路徑是否存在(不區分大小寫)
if os.path.exists(dirPath) == False:
print dirPath + ' is non-existent!'
return ''
#判斷路徑是否爲目錄(不區分大小寫)
if os.path.isdir(dirPath) == False:
print dirPath + ' is not a directory!'
return ''
return dirPath
def WalkDirReport(dirPath, fileNum):
print '##############' + str(fileNum) + ' files processed##############'
def WalkDir(dirPath):
dirPath = ValidateDir(dirPath)
if not dirPath:
return
#遍歷路徑下的文件及子目錄
fileNum = 0
for root, dirs, files in os.walk(dirPath):
for file in files:
#處理文件
#ChangeNames(root, file)
#RestoreNames(root, file)
fileNum += 1
while(fileNum % 100) == 0:
prompt = '$' + str(fileNum) + ' files processed, ' \
+ '''pause for checking. Type 'c' to continue: '''
if raw_input(prompt) == 'c':
break
WalkDirReport(dirPath, fileNum)
其中,ValidateDir()
用於校驗路徑合法性,同時還將非Unicode路徑轉爲Unicode路徑(該步驟也可由使用者自行完成)。
os.listdir()
方法遍歷目錄則較爲"笨拙",對比如下:
def WalkDir_unsafe(dirPath):
dirPath = ValidateDir(dirPath)
if not dirPath:
return
#遍歷路徑下的文件及子目錄
fileNum = 0
nameList = os.listdir(dirPath)
for name in nameList:
path = os.path.join(dirPath, name)
#類型爲目錄,遞歸(存在棧溢出風險)
if os.path.isdir(path) == True:
fileNum += WalkDir_unsafe(path)
continue
#處理文件
'''此時name等效於os.path.basename(path),即文件名;
dirPath等效於os.path.dirname(path),即目錄名;
path等效於os.path.abspath(os.path.basename(path)),即絕對路徑'''
fileNum += 1
return fileNum
因爲採用遞歸處理,所以該方法存在棧溢出風險(不過作者尚未遇到這種情況)。使用時,需按照如下方式調用:
WalkDirReport(r'E:\Pytest\測試', WalkDir_unsafe(r'E:\Pytest\測試'))
注意,雖然os.walk()
本身仍由os.listdir()
遞歸實現,但卻是生成器(generator)寫法,相比普通遞歸更節省內存資源。
遍歷目錄調試通過後,就可着手實現目錄和文件更名。爲簡單起見,更名規則爲"尾部添0",即"E:\a\b.txt"
會轉換爲"E:\a0\b.txt0"
。同時提供恢復函數,以便反覆調試。代碼如下:
def ChangeNames(dir, file):
#將'E:\a\b.txt'轉換爲'E:\a0\b.txt0',以此類推
filePath = os.path.join(dir, file)
newdir = dir.split('\\')
newdir[1:] = map(lambda x: x+'0', newdir[1:]) #盤符不變
newdir = '\\'.join(newdir)
ufilePath = os.path.join(newdir, file) + '0'
print filePath + ' => ' + ufilePath
os.renames(filePath, ufilePath)
def RestoreNames(dir, file):
#將'E:\a0\b.txt0'恢復爲'E:\a\b.txt',以此類推
filePath = os.path.join(dir, file)
newdir = dir.split('\\')
newdir[1:] = map(lambda x: x[:-1], newdir[1:])
newdir = '\\'.join(newdir)
ufilePath = os.path.join(newdir, file)[:-1]
print filePath + ' => ' + ufilePath
os.renames(filePath, ufilePath)
可見,"添0"和恢復的方法比較"笨拙"。但作爲Python新手,作者暫時只能如此。結合遍歷代碼,ChangeNames()
和RestoreNames()
可有效地更名和恢復。
注意os.renames()
方法,該方法可對嵌套目錄及其文件更名,可能會創建臨時目錄以存放新命名的子目錄和文件。因此,若以WalkDir(r'E:\bPytest')
方式調用且bPytest
目錄下存在空的子目錄,則更名後該子目錄保持原名原位置(仍在E:\bPytest
目錄下),而其他子目錄及文件被更名且"轉移"到E:\bPytest0
目錄下——同時出現bPytest
和bPytest0
兩個目錄!
3.2.4 消除單個文件名亂碼
遍歷和更名調試成功後,接下來便是重中之重——亂碼文件名恢復。
瀏覽各種網絡資料後,作者終於在Stackoverflow
網站上一則問答Getting
correct utf8 Chinese characters from messed-up iso-8859-1 in Python and MySQL裏找到一線曙光。回答者通過u'最'.encode('cp1252').decode('utf8')
成功地將最
轉換爲"最"
——這與作者遇到的亂碼何其相似!
在嘗試cp1252
、cp1254
...等衆多Windows編碼後,作者終於找到正確的編碼格式,即latin_1
,別名iso-8859-1
, iso8859-1
, 8859
, cp819
, latin
, latin1
, L1
。測試代碼如下:
print u'项ç®ä¸éæCUnitè¿è¡å¼åæµè¯'.encode('latin_1').decode('utf8')
print u'ãNBCå¤é´æ°é»(2011-2å£)'.encode('latin_1').decode('utf8')
print 'æ°æµªå客'.decode('utf8').encode('latin_1').decode('utf8')
其中,u'string'
等效於'string'.decode('utf8')
。運行結果爲:
項目中集成CUnit進行開發測試
《NBC夜間新聞(2011-2季)
新浪博客
經人工檢驗,完全符合期望!
此時再回想Roly-Poly提供的Java轉碼語句:
String str = new String(new String(messyName.getBytes("ISO-8859-1"), "GBK").getBytes("GBK"), "UTF-8");
因爲字符串在Java內存中以Unicode編碼存儲,且getBytes()
和new
String()
分別對應Python裏的encode()
和decode()
,所以等效的Python轉碼語句如下:
str = u'messyName'.encode('latin_1').decode('gbk').encode('gbk').decode('utf8')
從字符編碼規則可知,經過GBK編解碼"中轉"後,很可能出現data loss。以第一章的兩個文件名爲例,其Python轉碼如下:
s1 = u'GNU Readlineåºå½æ°çåºç¨ç¤ºä¾'.encode('latin_1').decode('gbk').encode('gbk','replace').decode('utf8')
s2 = u'å®æ¤è¿ç¨æ¥æ¶ç»ç«¯è¾å
¥çä¸ç§åéæ§æ¹æ³'.encode('latin_1').decode('gbk','replace').encode('gbk','replace').decode('utf8','replace')
s3 = u'å®æ¤è¿ç¨æ¥æ¶ç»ç«¯è¾å
¥çä¸ç§åéæ§æ¹æ³'.encode('latin_1').decode('utf8')
print s1 #GNU Readline庫函數的應用示例
print s2 #守護進程接收終�?輸入的一種變通�?方法
print s3 #守護進程接收終端輸入的一種變通性方法
其中,'replace'
參數以適當的字符替換編解碼過程中無法識別的字符,否則將產生UnicodeDecodeError
和UnicodeEncodeError
異常。可見,就本文問題而言,並不需要GBK編解碼"中轉"。
3.2.5 消除單級目錄下文件名亂碼
文件名字符串亂碼恢復成功後,接下來將對文件更名。該步驟需要在掛接移動硬盤的Windows 7主機上進行,因爲在Windows XP主機上文件名亂碼無法以期望的方式顯示和解析。例如:
最後那個以亂碼字符串爲名創建的文件,無法打開(UEStudio提示"含有一個無效的路徑",記事本提示"無效的窗口句柄"),os.rename()
也會報錯。
在Windows 7主機上,作者從移動硬盤拷貝一個單級亂碼目錄至磁盤作調試用。然後,編寫代碼恢復該目錄下的所有亂碼文件名:
from nt import chdir
def RecodeName(dirPath):
nameList = os.listdir(dirPath)
for fileName in nameList:
try:
ufileName = fileName.encode('latin_1').decode('utf8')
except UnicodeEncodeError as e:
print '[e]' + fileName + '(Possibly needn\'t xcode!)'
continue
print (fileName + ' => ' + ufileName)
#rename之前要先用chdir()函數進入到目標文件所在的路徑
chdir(dirPath)
os.rename(fileName, ufileName)
因爲存在某些文件已被手工更名的可能性,encode('latin_1')
時會拋出UnicodeEncodeError
異常,所以需要跳過這些文件。
運行結果如下:
8.26å京990å°ç»éªï¼å¸æ对以åç人æå¸®å© - è±è¯æä¸èè¯(TOEIC) - 大家论å -.url => 8.26南京990小經驗,希望對以後的人有幫助 - 英語託業考試(TOEIC) - 大家論壇 -.url
~2011.5.29~æä¸905ææ³~æ谢大家ç½è®ºå~å¸æè½ç»å¤§å®¶å¸¦æ¥å¸®å© - è±è¯æä¸èè¯(TOEIC) - 大家论å -.url => ~2011.5.29~託業905感想~感謝大家網論壇~希望能給大家帶來幫助 - 英語託業考試(TOEIC) - 大家論壇 -.url
ä¸å½é
æç½.url => 中國雅思網.url
å¦ä½æé«è±è¯å¬å_æé«è±è¯å¬åçæ¹æ³_å¬å课å .url => 如何提高英語聽力_提高英語聽力的方法_聽力課堂.url
>>>
經驗證,指定目錄下所有亂碼文件名均正確恢復。
3.2.6 消除嵌套目錄名及其文件名亂碼
單級目錄下文件名亂碼消除後,作者換用os.renames()
方法消除嵌套目錄名及其文件名。代碼如下:
def RecodeNames(dir, file):
filePath = os.path.join(dir, file)
try:
filePathNew = filePath.encode('latin_1').decode('utf8')
except UnicodeEncodeError as e:
'''os.renames()會創建臨時目錄以存放新命名的子目錄和文件,故此處異常
表明最底層文件已解碼成功(可能是手工處理),但其目錄路徑仍未解碼。
因此,需構造已解碼全路徑,以便renames將上述文件拷貝至新目錄。
'''
print '[e]' + filePath + '(Possibly needn\'t xcode!)'
filePathNew = os.path.join(dir.encode('latin_1').decode('utf8'), file)
print (filePath + ' => ' + filePathNew)
os.renames(filePath, filePathNew)
以WalkDir(r'F:\Pytest\Study')
運行,結果如下:
F:\Pytest\Study\John_æ°æµªå客.url => F:\Pytest\Study\John_新浪博客.url
[e]F:\Pytest\Study\ABR\~2011.5.29~託業905感想~感謝大家網論壇~希望能給大家帶來幫助 - 英語託業考試(TOEIC) - 大家論壇 -.url(Possibly needn't xcode!)
F:\Pytest\Study\ABR\~2011.5.29~託業905感想~感謝大家網論壇~希望能給大家帶來幫助 - 英語託業考試(TOEIC) - 大家論壇 -.url => F:\Pytest\Study\ABR\~2011.5.29~託業905感想~感謝大家網論壇~希望能給大家帶來幫助 - 英語託業考試(TOEIC) - 大家論壇 -.url
F:\Pytest\Study\å¾
读\ãNBCå¤é´æ°é»(2011-2å£)ã(NBC Nightly News)([m4v]è±è¯å¬åä¸è½½ -å¦ä¹ èµæåº.url => F:\Pytest\Study\待讀\《NBC夜間新聞(2011-2季)》(NBC Nightly News)([m4v]英語聽力下載 -學習資料庫.url
##############3 files processed##############
可見,根目錄和子目錄及所有下屬文件均正確更名。
讀者可能已經注意到,上述根目錄路徑名均爲英文字符。假如該路徑包含已解碼的中文目錄名(可能是手工處理)呢?例如將Study
目錄名改爲外語學習
。顯然,encode('latin_1')時會觸發UnicodeEncodeError
異常。因此,編解碼時需要跳過這些中文目錄名:
def RecodeNames(dir, file):
filePath = os.path.join(dir, file)
#對路徑解碼
paths = filePath.split('\\')
for i in range(len(paths)):
try:
paths[i] = paths[i].encode('latin_1').decode('utf8')
except UnicodeEncodeError as e:
#路徑可能出現已解碼的中文(可能是手工處理),不再重複解碼
continue
filePathNew = '\\'.join(paths)
print (filePath + ' => ' + filePathNew)
os.renames(filePath, filePathNew)
什麼?假如目錄名包含部分亂碼部分中文字符?拜託,這種情況不可能出現。
3.2.7 恢復原始亂碼目錄和文件名
激動人心的時刻來到了!本節將正式處理移動硬盤中的原始亂碼目錄和文件名。基於以上調試結果,最終的亂碼恢復代碼如下:
failedNamesList = []
def RecordFailedNames(name):
failedNamesList.append(name)
import ctypes
def RecodeNames(dir, file):
filePath = os.path.join(dir, file)
#對路徑解碼
paths = filePath.split('\\')
for i in range(len(paths)):
try:
paths[i] = paths[i].encode('latin_1').decode('utf8')
except UnicodeEncodeError as e:
#路徑可能出現已解碼的中文(可能是手工處理),不再重複解碼
continue
filePathNew = '\\'.join(paths)
print (filePath + ' => ' + filePathNew)
try:
os.renames(filePath, filePathNew)
except WindowsError as e:
print "[e]WindowsError({0}): {1}".format(e.winerror, ctypes.FormatError(e.winerror))
RecordFailedNames(filePath)
def WalkDirReport(dirPath, fileNum):
print '##############' + str(fileNum) + ' files processed##############'
print 'Failed files(%d):' %(len(failedNamesList))
for i in range(len(failedNamesList)):
print ' ' + failedNamesList[i]
某些亂碼文件名過長(多爲.mht文件),os.renames()
時會觸發WindowsError[3]
異常,如:
事實上,在資源管理器裏打開這些文件時,會提示"源路徑過長":
上述代碼可有效地恢復原始亂碼目錄和文件名。當時的運行截圖如下:
以及
經初步檢驗,更名成功。此時可通過tree
命令生成生成目錄樹,與原先保存的文件列表對比。遺憾的是,兩者樹狀列表順序不同,無法直接對比。當然,如果夠閒,也可以編程比照。
四. 後記
本文所述的亂碼恢復實踐其實是種"摸着石頭過河(trial and error)"的過程。如果當初先研究字符編碼,而不是左右開弓,應該會避免不少彎路。當然論及性價比,孰優孰劣,亦未可知。
最後,關於Python字符編碼基礎知識,作者將在後續文章中加以說明,同時澄清本文一些謬誤之處。臉皮薄,就不說"敬請期待"了~~