部分软件默认在C盘占用了很大的磁盘. 将文件夹迁移到其他盘,原地留下软连接,软件启动的之后,会访问到迁移后的位置,不影响软件适合用poython脚本importctypesimportosimportshutilimportsubprocessimportthreadingimporttkinterastkfromctypesimportwintypesfrompathlibimportPathfromtkinterimportfiledialog,messagebox,ttk SEE_MASK_NOCLOSEPROCESS0x00000040SW_HIDE0ERROR_CANCELLED1223classSHELLEXECUTEINFO(ctypes.Structure):_fields_[(cbSize,wintypes.DWORD),(fMask,ctypes.c_ulong),(hwnd,wintypes.HWND),(lpVerb,wintypes.LPCWSTR),(lpFile,wintypes.LPCWSTR),(lpParameters,wintypes.LPCWSTR),(lpDirectory,wintypes.LPCWSTR),(nShow,ctypes.c_int),(hInstApp,wintypes.HINSTANCE),(lpIDList,ctypes.c_void_p),(lpClass,wintypes.LPCWSTR),(hkeyClass,wintypes.HKEY),(dwHotKey,wintypes.DWORD),(hIconOrMonitor,wintypes.HANDLE),(hProcess,wintypes.HANDLE),]defnormalized(path):returnos.path.normcase(os.path.abspath(path))defis_same_or_child(path,parent):try:returnos.path.commonpath([normalized(path),normalized(parent)])normalized(parent)exceptValueError:returnFalsedefrun_mklink_as_admin(link_path,target_path):commandsubprocess.list2cmdline([/c,mklink,/D,str(link_path),str(target_path)])infoSHELLEXECUTEINFO()info.cbSizectypes.sizeof(info)info.fMaskSEE_MASK_NOCLOSEPROCESS info.lpVerbrunasinfo.lpFilecmd.exeinfo.lpParameterscommand info.nShowSW_HIDEifnotctypes.windll.shell32.ShellExecuteExW(ctypes.byref(info)):errorctypes.get_last_error()iferrorERROR_CANCELLED:raiseRuntimeError(已取消管理员授权。)raisectypes.WinError(error)try:ctypes.windll.kernel32.WaitForSingleObject(info.hProcess,0xFFFFFFFF)exit_codewintypes.DWORD()ifnotctypes.windll.kernel32.GetExitCodeProcess(info.hProcess,ctypes.byref(exit_code)):raisectypes.WinError()ifexit_code.value!0:raiseRuntimeError(fmklink 执行失败退出代码{exit_code.value})finally:ctypes.windll.kernel32.CloseHandle(info.hProcess)classFolderMigrator(tk.Tk):def__init__(self):super().__init__()self.title(文件夹迁移)self.geometry(680x250)self.minsize(600,250)self.source_vartk.StringVar()self.target_vartk.StringVar()self.status_vartk.StringVar(value请选择原文件夹和目标文件夹)self.columnconfigure(1,weight1)self._build_ui()def_build_ui(self):padding{padx:14,pady:10}ttk.Label(self,text原文件夹).grid(row0,column0,stickyw,**padding)ttk.Entry(self,textvariableself.source_var).grid(row0,column1,stickyew,pady10)ttk.Button(self,text选择...,commandself.choose_source).grid(row0,column2,**padding)ttk.Label(self,text目标文件夹).grid(row1,column0,stickyw,**padding)ttk.Entry(self,textvariableself.target_var).grid(row1,column1,stickyew,pady10)ttk.Button(self,text选择...,commandself.choose_target).grid(row1,column2,**padding)ttk.Separator(self).grid(row2,column0,columnspan3,stickyew,padx14,pady6)ttk.Label(self,textvariableself.status_var).grid(row3,column0,columnspan3,stickyw,padx14,pady8)self.migrate_buttonttk.Button(self,text迁移并创建链接,commandself.start_migration)self.migrate_button.grid(row4,column0,columnspan3,pady14)defchoose_source(self):pathfiledialog.askdirectory(title选择要迁移的原文件夹)ifpath:self.source_var.set(path)defchoose_target(self):pathfiledialog.askdirectory(title选择迁移到的目标文件夹)ifpath:self.target_var.set(path)defvalidate_paths(self):source_textself.source_var.get().strip()target_textself.target_var.get().strip()ifnotsource_textornottarget_text:raiseValueError(请选择原文件夹和目标文件夹。)sourcePath(source_text)target_parentPath(target_text)ifnotsource.is_dir():raiseValueError(原文件夹不存在或不是文件夹。)ifsource.is_symlink():raiseValueError(原文件夹已经是符号链接不能再次迁移。)ifnottarget_parent.is_dir():raiseValueError(目标文件夹不存在或不是文件夹。)destinationtarget_parent/source.nameifnormalized(source)normalized(destination):raiseValueError(目标位置与原文件夹位置相同。)ifdestination.exists()ordestination.is_symlink():raiseFileExistsError(f目标文件夹中已经存在同名项目\n{destination})ifis_same_or_child(target_parent,source):raiseValueError(目标文件夹不能位于原文件夹内部。)returnsource,destinationdefstart_migration(self):try:source,destinationself.validate_paths()except(ValueError,FileExistsError)asexc:messagebox.showerror(无法迁移,str(exc))returnconfirmedmessagebox.askyesno(确认迁移,将执行以下操作\n\nf剪切{source}\nf到{destination}\n\nf然后创建目录链接\n{source}-{destination},)ifnotconfirmed:returnself.migrate_button.config(statedisabled)self.status_var.set(正在迁移文件夹请勿关闭程序...)threading.Thread(targetself.migrate,args(source,destination),daemonTrue).start()defmigrate(self,source,destination):movedFalsetry:shutil.move(str(source),str(destination))movedTrueself.after(0,self.status_var.set,迁移完成正在请求管理员权限...)run_mklink_as_admin(source,destination)exceptExceptionasexc:rollback_errorNoneifmovedanddestination.exists()andnotsource.exists():try:shutil.move(str(destination),str(source))exceptExceptionasrollback_exc:rollback_errorrollback_exc textstr(exc)ifrollback_error:text(\n\n自动还原也失败了请手动处理f\n当前文件夹{destination}f\n还原位置{source}f\n原因{rollback_error})elifmoved:text\n\n已将文件夹自动移回原位置。self.after(0,self.finish_error,text)returnself.after(0,self.finish_success,source,destination)deffinish_success(self,source,destination):self.migrate_button.config(statenormal)self.status_var.set(迁移和目录链接创建成功)messagebox.showinfo(迁移完成,f文件夹已迁移到\n{destination}\n\nf原位置已创建目录链接\n{source},)deffinish_error(self,text):self.migrate_button.config(statenormal)self.status_var.set(迁移失败)messagebox.showerror(迁移失败,text)if__name____main__:ifos.name!nt:raiseSystemExit(此工具仅支持 Windows。)FolderMigrator().mainloop()运行命令我的脚本名字叫 folder_migrator.pypython folder_migrator.py打包命令python-m PyInstaller-F-w-n磁盘迁移folder_migrator.py使用选择原文件夹和目标的文件夹,点击按扭之后,会弹窗进行确认2. 点击确认,会迁移文件并调用cmd的管理员命令进行创建连接,若是原文件夹的迁移需要管理员权限,启动软件的时候,