python spawn cmd脚本并在结束后关闭它
Posted
技术标签:
【中文标题】python spawn cmd脚本并在结束后关闭它【英文标题】:python spawn cmd script and close it after it ends 【发布时间】:2022-01-11 19:44:54 【问题描述】:我编写了自动更新软件(使用 pyinstaller 编译为 exe 的 python)。一切正常,但有一个问题我无法解决 - 完成更新后我无法关闭 cmd 窗口。
假设软件exe名称是software.exe
(这个软件是单文件exe)。启动时,它会检查更新,如果找到,它将下载为software.bundle
,并将在当前目录上创建一个临时脚本software-update.bat
。此脚本注册为在software.exe
关闭时调用(使用atexit.register)。
此脚本将删除software.exe
并将software.bundle
重命名为sofware.exe
,将启动software.exe
并将自行删除software-update.bat
。
所以更新正在运行。
问题是,为了删除.exe我需要先完全关闭它,这意味着执行software-update.bat
的命令需要与主运行的.exe分开运行software.exe
。
我只能让它工作
os.system(f'start script_update_name /b')
(再次 - 此命令开始 software-update.bat
然后 software.exe
'立即' 退出)
在尝试从更新脚本中删除 sofware.exe
时,尝试使用 subprocess.run
或任何替代方法只会导致“访问被拒绝”(因为 software.exe
显然仍在运行)。
所以最后我的问题,解决上述之一将解决我的问题:
-
是否有可靠退出因
start ...
命令而声明的cmd 窗口的选项?在脚本末尾添加exit
(或我能找到的任何其他解决方案)不起作用。
是否有替代“开始”的方法?知道将 bat 文件与主 exe 分开运行,但完成后却退出?
如果有帮助,这里是更新脚本software-update.bat
:
with open(script_update_name, 'w') as f:
s = dedent(f"""
@echo off
echo Updating...
del file.__str__()
rename (file.parent / done_download_name).__str__() osPath.basename(file.__str__())
start osPath.basename(file.__str__())
echo Done
del script_update_name
""").strip('\n')
f.write(s)
我知道这听起来很简单,但老实说,我现在无法解决它。
任何帮助将不胜感激!
【问题讨论】:
可以使用os.system(f'start "Updating Software" %SystemRoot%\System32\cmd.exe /D /C "script_update_name"')
,其中script_update_name
应该扩展为具有完整路径的字符串。 os.system()
在 Windows 上导致在后台启动 %SystemRoot%\System32\cmd.exe
并带有选项 /c
和 Python 代码中的字符串作为命令行以使用一个或多个参数执行。在这种情况下,命令行使用多个参数传递给cmd.exe
,并且必须考虑在命令提示符窗口中运行cmd /?
时的帮助输出所解释的条件。
Python 等待 Python 代码的进一步处理,直到由 Python 本身以 os.system()
启动的 cmd.exe
自行终止,这发生在成功启动另一个 cmd.exe
实例并打开一个新的控制台窗口和与第一个 cmd.exe
和 Python 编码的可执行文件并行运行。因此,批处理文件需要额外的代码来检查任务列表并等待 Python 可执行文件的自我终止,然后才能替换它,或者它只是等待一秒钟,这希望总是有足够的时间自动关闭第一个 cmd.exe
和Python 可执行文件的下一个。
批处理文件应该被编码为使用最后一行删除自己,这是可能的,因为批处理文件是由cmd.exe
处理的,在命令行之前打开它,读取下一行并关闭它处理和执行。所以批处理文件不会一直打开,cmd.exe
可以使用写入批处理文件本身的命令删除批处理文件。见How to make a batch file delete itself?
顺便说一句:os.system 已弃用。应该使用subprocess module。我建议先阅读 Windows 内核库函数 CreateProcess 的 Microsoft 文档。 cmd.exe
使用它来运行任何不使用或使用 start
的可执行文件,Python 也使用它来使用 subprocess
模块的函数和启动另一个 exe 的任何其他 Windows 可执行文件。
在阅读了CreateProcess
,当然还有STARTUPINFO 结构之后,应该阅读和使用subprocess
模块的文档,应该是subprocess.Popen
和STARTUPINFO
只运行一个cmd.exe
上面发布的参数为 DETACHED_PROCESS,这意味着无需 Python 等待启动 cmd.exe
的自动关闭(只需要一个)。 os.environ['SystemRoot']
可用于获取与 "\\System32\\cmd.exe"
连接的 Windows 目录的路径。
【参考方案1】:
我可以用@Mofi 的非常有用的 cmets 解决问题:
问题是时间问题 - 我需要在启动 .bat 脚本之前完全终止 .exe 进程,因为 .bat 脚本正在积极尝试删除 .exe 文件。为此我在我的python代码中使用os.getpid()
(以.exe运行)来获取.exe的当前进程pid,.bat文件等待这个PID进程退出,完整的脚本是:
with open(script_update_name, 'w') as f:
s = dedent(f"""
@echo off
REM wait for process to finish
:loop
tasklist | find " os.getpid() " >nul
if not errorlevel 1 (
timeout /t 1 /nobreak >nul
echo waiting for EasySchema to exit...
goto :loop
)
echo Updating EasySchema...
del file.__str__()
rename (file.parent / done_download_name).__str__() osPath.basename(file.__str__())
start osPath.basename(file.__str__())
echo Done
del script_update_name
""").strip('\n')
f.write(s)
运行脚本的部分已从 os.system() subprocess.Popen 更改为 @Mofi 建议
def run_update_script():
subprocess.Popen(['cmd', '/C', file.parent / script_update_name])
有了这个,我可以可靠地更新我的 .exe 并在结束后关闭终端。
【讨论】:
以上是关于python spawn cmd脚本并在结束后关闭它的主要内容,如果未能解决你的问题,请参考以下文章
运行python程序turtle画图,cmd的方式,画完成之后图形窗口会自动关闭。同样的程序在 IDEL中不会