【问题标题】:Having trouble reading and writing unicode/non-ascii characters to file in python在 python 中读取和写入 unicode/非 ascii 字符到文件时遇到问题
【发布时间】:2020-04-01 19:31:52
【问题描述】:

我有一个目录结构,其中包含许多带有非 ascii 字符的目录,主要是梵文。我正在为脚本中的这些目录/文件编制索引,但不知道如何最好地处理这些实例。这是我的流程:

  • 以递归方式散列所有文件,将每个文件的路径、文件名和散列写入 .tsv 文件。
  • 浏览此文件,根据是否存在重复的哈希对每一行进行排序。生成具有以下形式的字典:{'path': columns[0], 'filename': columns[1], 'status': True},其中 status 确定稍后是否对文件执行操作。
  • 浏览此字典,将重复项从其原始位置移出并移至偏移根路径(例如,./duplicates 而不是 ./)。
  • 为每次移动写入一个文件,运行一个命令,如果需要,将反转移动(只是mv a b);这并不重要,但我想我会把它包括在内。

以下是一些示例数据以及我目前所写的内容:

生成的 tsv 示例(路径/名称/哈希):

./Personal Research/Ramnad 9"14"10  DSC_0004.JPG    850cd9dcb0075febd4c0dcd549dd7860        
./Personal Research/Ramnad 9"14"10  DSC_0010.JPG    9db2219fc4c9423016fb9e295452f1ad        
./Personal Research/Ramnad 9"14"10  DSC_0006.JPG    ef7d13b88bbaabc029390bcef1319bb1            

" 实际上是 unicode:

块:私人使用区
Unicode: U+F019
UTF-8: 0xEF 0x80 0x99
JavaScript: 0xF019

代码: 将以上内容写入文件(fulltsv):

for root, dirs, files in os.walk(SRC_DIR, topdown=True):
        files[:] = [f for f in files if any(ext in f for ext in EXT_LIST) if not f.startswith('.')]
        for file in files:
            with open(os.path.join(root,file),'r') as f:
                with open(SAVE_DIR+re.sub(r'\W+', '', os.path.basename(root).lower())+'.tsv', 'a') as fout:
                    writer = csv.writer(fout, delimiter='\t', quotechar='\"', quoting=csv.QUOTE_MINIMAL)
                    checksums = []
                    with open(os.path.join(root, file), 'rb') as _file:
                        checksums.append([root, file, hashlib.md5(_file.read()).hexdigest()])
                        writer.writerows(checksums)

从该文件中读取:

#       generate list of all tsv
for (dir, subs, files) in os.walk(ROOT):
    #   remove the new-root from the search
    subs = [s for s in subs if NROOT not in s]
    for f in files:
        fpath = os.path.join(dir,f)
        if ".tsv" in fpath:
            TSVLIST.append(fpath)

#       open/append all TSV content to a single new TSV
with open(FULL,'w') as wfd:
    for f in TSVLIST:
        with open(f,'r') as fd:
            wfd.write(fd.read())
            lines = sum(1 for line in f)

#   add all entries to a dictionary keyed to their hash
entrydict = {}

ec = 0
with open(FULL, 'r') as fulltsv:
    for line in fulltsv:
        columns = line.strip().split('\t')
        if not columns[2].startswith('.'):
            if columns[2] not in entrydict.keys():
                entrydict[str(columns[2])] = []

            entrydict[str(columns[2])].append({'path': columns[0], 'filename': columns[1], 'status': True})
            if len(entrydict[str(columns[2])]) > 1:
                ec += 1

ed = {k:v for k,v in entrydict.items() if len(v)>=2}

移动重复:

 for e in f:
            if len(f)-mvcnt > 1:
                if e['status'] is True:
                    p = e['path']    #   path
                    n = e['filename']   #   name
                    n0,n0ext = os.path.splitext(n)
                    n1 = n

                    #   directory structure for new file
                    FROOT = p.replace(p.split('/')[0],NROOT,1)
n1 = n

                    rebk = 'mv {0}/{1} {2}/{3}'.format(FROOT,n,p,n)
                    shutil.move('{0}/{1}'.format(p,n),'{0}/{1}'.format(FROOT,n))
                    dupelist.write('{0} #{1}\n'.format(rebk,str(h)))
                    mvcnt += 1

我遇到的错误:

Traceback (most recent call last):
  File "/usr/lib/python3.6/shutil.py", line 550, in move
    os.rename(src, real_dst)
FileNotFoundError: [Errno 2] No such file or directory: '"./Personal Research/Ramnad 9""14""10"/DSC_0003.NEF' -> './duplicateRoot/Personal Research/Ramnad 9""14""10"/DSC_0003.NEF'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "dCompare.py", line 164, in <module>
    shutil.move('{0}/{1}'.format(p,n),'{0}/{1}'.format(FROOT,n))
  File "/usr/lib/python3.6/shutil.py", line 564, in move
    copy_function(src, real_dst)
  File "/usr/lib/python3.6/shutil.py", line 263, in copy2
    copyfile(src, dst, follow_symlinks=follow_symlinks)
  File "/usr/lib/python3.6/shutil.py", line 120, in copyfile
    with open(src, 'rb') as fsrc:
FileNotFoundError: [Errno 2] No such file or directory: '"./Personal Research/Ramnad 9""14""10"/DSC_0003.NEF'

显然这与我处理 unicode 字符的方式有关,但我以前从未使用过这个,并且不确定在什么时候/我应该如何处理文件名。在适用于 linux、python 3 的 windows 子系统下使用 ubuntu 10。

【问题讨论】:

  • 它与问题没有直接关系,但可能值得使用pathlib
  • 我在您提供的源代码清单中没有看到with open(src, 'rb') as fsrc:。你如何构建字符串src
  • @HeatfanJohn 来自 shutil.py
  • @AMC 我会的,但我有兴趣了解我遇到的问题的原因,而不是仅仅扔模块。

标签: python unicode filenames


【解决方案1】:

我在阅读堆栈跟踪时看到的一个问题是,鉴于 OP 的示例 TSV,Unicode 字符是错误的(它们不存在):

FileNotFoundError: [Errno 2] No such file or directory: '"./Personal Research/Ramnad 9""14""10"/DSC_0003.NEF' -> './duplicateRoot/Personal Research/Ramnad 9""14""10"/DSC_0003.NEF'

在我认为不应该存在的源路径和目标路径中有一些引号转义,额外的双引号,看起来路径被分解并再次连接(或其他东西):

'"./Personal Research/Ramnad 9""14""10"/DSC_0003.NEF'

我试图重新创建 OP 的错误,但不能。但是,当我处理下面的示例时,我最初得到了一个 FileNotFoundError(因为我缺少目标文件夹,因此在我的示例中是 os.makedirs()),但路径编码正确:

FileNotFoundError: [Errno 2] No such file or directory: 'foo/Personal Research/Ramnad 9\uf01914\uf01910/DSC_0006.JPG'

我只能猜测 TSV 文件或 entrydict 中的编码是否混乱。 OP,您是否在解释器中检查了该文件或 dict 并确认您在预期的路径中看到了 \uf019?也许像下面这样来确保这些代码点存在:

>>> print(path.encode('unicode_escape'))
b'./Personal Research/Ramnad 9\\uf01914\\uf01910'
>>> # or, look for 61465
>>> [ord(char) for char in path]
[46, 47, 80, 101, 114, 115, 111, 110, 97, 108, 32, 82, 101, 
115, 101, 97, 114, 99, 104, 47, 82, 97, 109, 110, 97, 100, 
32, 57, 61465, 49, 52, 61465, 49, 48]

这是我的尝试,它可能会有所帮助......

我创建了一个示例 TSV 文件和相应的目录结构:

>>> p='./Personal Research/Ramnad 9\uf01914\uf01910'
>>> os.makedirs(p)
>>> checksums=[[p, 'DSC_0006.JPG', 'hash']]
>>> with open('full.tsv', 'a') as fout:
    writer = csv.writer(fout, delimiter='\t', quotechar='\"', quoting=csv.QUOTE_MINIMAL)
    writer.writerows(checksums)

并在shell中触摸了文件:

$ touch Personal\ Research/Ramnad\ 91410/DSC_0006.JPG

检查 full.tsv 以确保它被正确写入:

$cat full.tsv
./Personal Research/Ramnad 91410  DSC_0006.JPG    hash

空块是正确 utf-8 编码的代码点,基于 OP 包含的 " 的 Unicode 描述。

hexdump -C full.tsv保证utf-8编码(找2组ef 80 99):

00000010  72 63 68 2f 52 61 6d 6e  61 64 20 39 ef 80 99 31  |rch/Ramnad 9...1|
00000020  34 ef 80 99 31 30 09 44  53 43 5f 30 30 30 36 2e  |4...10.DSC_0006.|

然后我跑了

>>> entrydict = {}

>>> ec = 0
>>> with open('full.tsv', 'r') as fulltsv:
    for line in fulltsv:
        columns = line.strip().split('\t')
        if not columns[2].startswith('.'):
            if columns[2] not in entrydict.keys():
                entrydict[str(columns[2])] = []

            entrydict[str(columns[2])].append({'path': columns[0], 'filename': columns[1], 'status': True})
            if len(entrydict[str(columns[2])]) > 1:
                ec += 1

>>> entrydict
{'hash': [{'path': './Personal Research/Ramnad 9\uf01914\uf01910', 'filename': 'DSC_0006.JPG', 'status': True}]}`

最后:

>>> e = entrydict['hash'][0]
>>> e
{'path': './Personal Research/Ramnad 9\uf01914\uf01910', 'filename': 'DSC_0006.JPG', 'status': True}
>>> NROOT='foo'
>>> if e['status'] is True:
    p = e['path']    #   path
    n = e['filename']   #   name
    n0,n0ext = os.path.splitext(n)
    n1 = n

    #   directory structure for new file
    FROOT = p.replace(p.split('/')[0],NROOT,1)


    rebk = 'mv {0}/{1} {2}/{3}'.format(FROOT,n,p,n)
    print(rebk)
    src='{0}/{1}'.format(p,n)
    dst='{0}/{1}'.format(FROOT,n)
    os.makedirs(FROOT)
    shutil.move(src,dst)

它奏效了。无赖。

【讨论】:

  • 这和我的基本一样。我将再次检查所有内容,并将根据您的结果检查我所拥有的内容,但我确实检查了并且目录似乎编码正确。如果我把它搞砸了,我想是在我重新阅读 TSV 的时候;是否将它们聚合在一起我不确定,但这是我的下一个重点。非常感谢您所做的所有工作,非常适合阅读。
猜你喜欢
  • 2021-04-01
  • 2015-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-06
  • 2019-02-21
  • 1970-01-01
相关资源
最近更新 更多