shutil.move 在新名称包含冒号(和扩展名)时删除 windows 上的文件

shutil.move deletes the file on windows when new name contains a colon (and an extension)

尝试:

import os, shutil

wd = os.path.abspath(os.path.curdir)
newfile = os.path.join(wd, 'testfile')
print str(newfile)
with open(newfile, 'w') as f: f.write('Hello bugs')
shutil.move(newfile, os.path.join(wd, 'testfile:.txt')) # note the :

现在检查目录 - 新文件已删除且未创建其他文件 - 进程已完成,退出代码为 0。

如果您发出:

shutil.move(newfile, os.path.join(wd, 'testfile:')) # note no extension

吹的是:

Traceback (most recent call last):
      File "C:/Users/MrD/.PyCharm40/config/scratches/scratch_3", line 9, in <module>
        shutil.move(newfile, os.path.join(wd, 'testfile:'))
      File "C:\_\Python27\lib\shutil.py", line 302, in move
        copy2(src, real_dst)
      File "C:\_\Python27\lib\shutil.py", line 130, in copy2
        copyfile(src, dst)
      File "C:\_\Python27\lib\shutil.py", line 83, in copyfile
        with open(dst, 'wb') as fdst:
    IOError: [Errno 22] invalid mode ('wb') or filename: 'C:\Users\MrD\.PyCharm40\config\scratches\testfile:'

这是应该的。

这是一个错误吗?

上下文:我正在测试我的代码在给出非法文件名时的行为(: 在 windows 文件名中是非法的)当令我惊讶的是我的程序删除了原始文件(糟糕!)并创建了一个具有原始属性的零大小文件(是的,在我的例子中,文件 创建的,只是空的)和文件名给 : 的文件名 - soo像 textfile:.jpg 这样的文件名给了我一个零字节 textfile。它进行了大量调试 - 这是 Python27\lib\shutil.py copyfile() 中的小动物(上面吹过但没有吹过的线):

我不知道为什么在我的情况下创建了文件,而 运行 脚本编号

这不是 Python 的 shutilos 模块中的错误,它只是 Windows 中的一个怪异之处。 Peter Wood 的 在评论中讨论了 "Advanced Data Streams" —— 一种 Windows 文件系统机制,它将包含元数据的隐藏文件附加到常规的可见文件。附上一个关键字;删除附加文件后,隐藏文件也会被删除。

好像用冒号来分隔普通文件和隐藏文件的路径。例如,如果您在命令行中写入:

> notepad foo

然后关闭记事本,写入

> notepad foo.txt:bar

记事本将打开隐藏文件。继续在里面写点东西,保存,然后关闭。输入 > dir,命令行将只显示 foo.txt,而不显示 foo.txt:bar.txt。但是果然,如果你写

> notepad foo.txt:bar.txt

您刚刚编辑的文件将出现,您的更改将完好无损。

那么您的 Python 代码发生了什么? shutil.move 的文档说:

src is copied (using shutil.copy2()) to dst and then removed.

所以当你将testfile移动到testfile:.txt时,Python首先将testfile复制到隐藏testfile:.txt.但随后它 删除了 testfile,并且通过这样做 删除了 隐藏的 testfile:.txt。因此在您看来原始文件已被删除,并且没有创建新文件。

下面的代码片段可能会使这一点更清楚(我将其保存为 demo.py,并且我将其 运行 保存在同一个目录中,否则为空目录):

import os, shutil


with open('test', 'w') as f:
    f.write('Hello bugs')

shutil.copy2('test', 'test:foo.txt')

with open('test:foo.txt') as f:
    print(f.read())

print 'test: exists? ', os.path.exists('test')
print 'test:foo.txt exists? ', os.path.exists('test:foo.txt')
print os.listdir('.')

print('removing...')
os.remove('test')

print 'test: exists? ', os.path.exists('test')
print 'test:foo.txt exists? ', os.path.exists('test:foo.txt')
print os.listdir('.')

这会打印:

Hello bugs
test exists? True
test:foo.txt exists? True
['demo.py', 'test']
removing...
test: exists? False
test:foo.txt exists? False
['demo.py']

这表明我们可以创建一个普通文件,写入它,然后将该普通文件复制到它的隐藏流中,打开并读取它就可以了,结果与预期的一样。然后我们看到 os.path.exists 表明 test 和它的隐藏附件 test:foo.txt 都存在,即使 os.listdir 只显示 test。然后我们删除 test,我们看到 test:foo.txt 也不再存在。

最后,您不能创建没有名称的隐藏数据流,因此 test: 是无效路径。 Python 在这种情况下正确抛出异常。

所以 Python 代码实际上在 Windows 下正常运行 -- "Alternate Data Streams" 只是一个鲜为人知的 "feature",这种行为令人惊讶。