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.PopenSTARTUPINFO 只运行一个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 windows脚本子进程在脚本结束后继续输出

期待错误处理 - spawn id未打开

运行python程序turtle画图,cmd的方式,画完成之后图形窗口会自动关闭。同样的程序在 IDEL中不会

立即从cmd获取结果而无需等待执行python脚本结束[重复]

项目启动报错spawn cmd ENOENT

python关闭 os.popen()的方法