关于python:shutil.move在新名称包含冒号(和扩展名)时删除Windows上的文件

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

尝试:

1
2
3
4
5
6
7
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 :

现在检查目录 - 删除newfile并且不创建其他文件 - 进程以退出代码0结束。

如果您发出以下情况:

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

它吹嘘:

1
2
3
4
5
6
7
8
9
10
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文件名中是非法的)令我惊讶的是我的程序删除了原始文件(坏!)并创建了一个零大小的文件,其属性为 原始(在我的情况下是的,文件是创建的,只是空的)和文件名,文件名给出: - 所以像textfile:.jpg这样的文件名给了我一个零字节textfile。 这需要大量的调试 - 这里是Python27 lib shutil.py copyfile()中的小动物(上面吹的线并没有吹过):

enter image description here

我不知道为什么在我的情况下文件是在运行脚本时创建的。


这不是Python的shutilos模块中的错误,它只是Windows中的一个奇怪现象。 Peter Wood在评论中的链接讨论了"高级数据流" - 一种Windows文件系统机制,它将包含元数据的隐藏文件附加到常规可见文件。附上一个关键词;如果删除了附加的文件,则删除隐藏文件。

看来冒号用于将常规文件的路径与隐藏文件分开。例如,如果在命令行中写入:

1
> notepad foo

然后关闭记事本,然后写

1
> notepad foo.txt:bar

记事本将打开隐藏文件。继续写下一些内容,保存并关闭。键入> dir并且命令行将仅显示foo.txt,而不是foo.txt:bar.txt。但果然,如果你写的话

1
> 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,并且我在相同的,其他方面的空目录中运行它):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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('.')

这打印:

1
2
3
4
5
6
7
8
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下运行 -"备用数据流"就是这样一个鲜为人知的"功能",这种行为令人惊讶。