如何在Python中移动文件?

1441

我该如何在Python中实现类似于mv命令的功能?

mv "path/to/current/file.foo" "path/to/new/destination/for/file.foo"

8
如果你熟悉 gnu-coreutils 的 mv 命令,那么需要注意 Python 中的 shutil.move 在一个特殊情况下与 mv 不同。具体信息请查看此处全文。简而言之,当目标文件夹已存在一个与源文件同名的文件时,Python 的 shutil.move 会引发异常(但 gnu-coreutils 的 mv 不会)。 - Trevor Boyd Smith
我觉得将问题编辑成与接受的答案相匹配是非常不公平的。该问题并没有要求将一个文件移动到另一个文件中,而是将一个或多个文件移动到一个目录中。 - Javier Palacios
11个回答

2156

os.rename()os.replace()shutil.move() 的语法相同。

import os
import shutil

os.rename("path/to/current/file.foo", "path/to/new/destination/for/file.foo")
os.replace("path/to/current/file.foo", "path/to/new/destination/for/file.foo")
shutil.move("path/to/current/file.foo", "path/to/new/destination/for/file.foo")
  • 必须在源和目标参数中都包含文件名("file.foo")。如果两者之间不同,则文件将被重命名并移动。
  • 正在创建新文件的目录必须已存在。
  • 在Windows上,如果该名称的文件已经存在,将引发异常,但是os.replace()即使出现这种情况也会静默地替换文件。
  • shutil.move在大多数情况下只是简单地调用os.rename。但是,如果目标位于与源不同的磁盘上,则会复制然后删除源文件。

10
我是唯一一个认为os.rename对目录不起作用的人吗?我引用一下:“如果dst是一个目录,将会抛出OSError。” - Fabian
63
shutil.move可以用于移动目录。您可以使用相对路径 shutil.move(f.name, "tmp/") 或完整路径 shutil.move(f.name, "/Users/hello/tmp/"),但在路径中不要使用 **~**,在Python2.7.9,Mac OS X中已检查。 - whyisyoung
7
~是一个shell的构造,与文件路径本身无关,除了作为一种错误的约定外。如果你真的想涉及到你的主目录,可以使用os.getenv('HOME'),必要时将其与所需路径的部分进行连接。 - Armen Michaeli
23
你可以始终使用 os.path.expanduser() 函数来根据操作系统的规则正确地扩展 '~' 符号。这种方法更加简洁,因为在 Windows 上 %HOME% 并不总是被设置。 - ig0774
26
如果源文件和目标文件不在同一个设备上,os.rename 无法处理。如果不确定源文件和目标文件是否在同一设备上,请使用 shutil.move - Giuseppe Scrivano
显示剩余6条评论

336

尽管 os.rename()shutil.move() 都可以重命名文件,但最接近 Unix mv 命令的命令是 shutil.move()。区别在于,os.rename() 如果源和目标在不同的磁盘上,则无法工作,而 shutil.move() 不受文件磁盘的影响。


107
如果目标路径在当前文件系统上,shutil.move() 将使用 os.rename() 进行移动。否则,shutil.move() 将使用 shutil.copy2() 将源文件复制到目标路径,然后删除源文件。 - fmalina
12
请注意,shutil.copy2() 无法复制所有文件元数据,因此如果发生这种情况,就像执行 cp -p 然后执行 rm 一样。 - 2rs2ts
11
注意:在Python 2.7.3中,如果目标文件已经存在,则shutil.move操作会失败。因此,如果可能出现这种情况,要么捕获该错误,要么手动删除文件/目录,然后再进行移动操作。 - Dana

130

1
最近我使用了以下代码来将所有的*.LNK(快捷方式)文件移动到一个名为DUMP的目录中:Path("path/to/current/file.foo").rename("path/to/new/destination/for/".joinpath(Path.name))。效果非常好! :D - Amar
10
这很完美,但是如果您想将文件从一个设备移动到另一个设备,它将失败(无效的跨设备链接)。 - capooti
1
@Amar 或许这个更好。Path("path/to/current/file.foo").rename(Path("path/to/new/destination/for") / Path.name)) - MoonFruit

45
无论是使用os.rename还是shutil.move,你都需要导入模块。 不需要使用*通配符来移动所有文件。
我们有一个名为awesome.txt的文件,它位于路径为/opt/awesome的source文件夹中。
in /opt/awesome
○ → ls
source
○ → ls source
awesome.txt

python 
>>> source = '/opt/awesome/source'
>>> destination = '/opt/awesome/destination'
>>> import os
>>> os.rename(source, destination)
>>> os.listdir('/opt/awesome')
['destination']

我们使用了os.listdir来查看文件夹名称是否确实更改。 这里是shutil将目标移回源位置。
>>> import shutil
>>> source = '/opt/awesome/destination' 
>>> destination = '/opt/awesome/source'
>>> shutil.move(source, destination)
>>> os.listdir('/opt/awesome/source')
['awesome.txt']

这次我检查了源文件夹,确保我创建的awesome.txt文件存在。它在那里。
现在我们已经将一个文件夹及其文件从源位置移动到目标位置,然后再移回来了。

6
这份文档表明,你在使用shutil.move方法时,参数位置可能搞反了。请检查一下参数顺序。 - mac10688
3
我把源文件和目标文件的位置颠倒了,从而看到文件是从源位置移动到目标位置再返回到源位置...我意识到这可能不太清晰。 - jmontross
示例中出现错误。src和dst被颠倒了! - Ludo Schmidt

30

这是我目前正在使用的:

import os, shutil
path = "/volume1/Users/Transfer/"
moveto = "/volume1/Users/Drive_Transfer/"
files = os.listdir(path)
files.sort()
for f in files:
    src = path+f
    dst = moveto+f
    shutil.move(src,dst)

你还可以将此转换为一个函数,接受源目录和目标目录,并在不存在目标文件夹时创建它并移动文件。还允许过滤源文件,例如如果您只想移动图像,则使用模式'*.jpg',默认情况下,它会移动目录中的所有东西。

import os, shutil, pathlib, fnmatch

def move_dir(src: str, dst: str, pattern: str = '*'):
    if not os.path.isdir(dst):
        pathlib.Path(dst).mkdir(parents=True, exist_ok=True)
    for f in fnmatch.filter(os.listdir(src), pattern):
        shutil.move(os.path.join(src, f), os.path.join(dst, f))

3
使用fnmatch.filter()可以轻松将此转换为过滤移动,请查看我的编辑。 另外,最好使用os.path.join(parent_path, filename)而不是字符串连接以避免跨平台问题。 - iggy12345

18

已被接受的答案并不正确,因为问题不是将文件重命名为文件,而是将多个文件移动到一个目录中。shutil.move可以完成此任务,但对于此目的,os.rename无用(如评论中所述),因为目标必须有显式的文件名。


不是无用的,只是需要更多的工作来移动多个文件。您可以使用 os.path.basename(my_file_path) 获取文件名,使用 os.path.dirname(my_file_path) 获取文件目录。此外,OP并没有非常清楚地说明他是否想要移动多个文件。他在问题中提到只移动一个文件,但他的示例代码暗示着移动多个文件。 - Jacques Mathieu

4

由于你不关心返回值,你可以这样做:

import os
os.system("mv src/* dest/")

2

使用subprocess.run()方法也是可行的。

python:
>>> import subprocess
>>> new = "/path/to/destination"
>>> old = "/path/to/new/destination"
>>> process = "mv ..{} ..{}".format(old,new)
>>> subprocess.run(process, shell=True) # do not remember, assign shell value to True.

在Linux上工作时这将有效。在Windows上可能会出现错误,因为没有mv命令。


1
为什么要调用外部进程,当Python已经有API可以实现这个功能? - Pranav Gupta

1
根据此处描述的答案(链接),使用subprocess是另一个选项。
像这样:
subprocess.call("mv %s %s" % (source_files, destination_folder), shell=True)

我很好奇这种方法与 shutil 相比的优缺点。由于在我的情况下,我已经因其他原因使用了 subprocess 并且它似乎有效,我倾向于坚持使用它。
这取决于您运行脚本的shell。 mv 命令适用于大多数Linux shell(bash,sh等),但也适用于Windows上的Git Bash之类的终端。对于其他终端,您需要将 mv 更改为替代命令。

8
我认为这取决于操作系统。我认为在Windows操作系统上使用mv命令并不能成功。 - Joshua Schlichting
1
@JoshuaSchlichting 这更依赖于shell而不是平台类型。例如,在Windows上,这将在Git Bash终端中正常工作,但不适用于Cmd。 - LightCC
@LightCC 很好的发现!谢谢! - Joshua Schlichting
我在最后一段更新了正确的信息,而不是一个问题,如果它是系统相关的。 - LightCC

0

这是一个解决方案,它不允许使用mv命令来执行shell

from subprocess import Popen, PIPE, STDOUT

source = "path/to/current/file.foo", 
destination = "path/to/new/destination/for/file.foo"

p = Popen(["mv", "-v", source, destination], stdout=PIPE, stderr=STDOUT)
output, _ = p.communicate()
output = output.strip().decode("utf-8")
if p.returncode:
    print(f"E: {output}")
else:
    print(output)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接