使用start命令启动GUI应用时的批处理输出重定向
这是一个场景:
我们有一个Python脚本,它会启动一个Windows批处理文件,并把输出内容重定向到一个文件里。之后,它会读取这个文件,然后尝试删除它:
os.system(C:\batch.bat >C:\temp.txt 2>&1)
os.remove(C:\temp.txt)
在batch.bat文件中,我们这样启动一个Windows图形界面程序:
start c:\the_programm.exe
这就是批处理文件里的所有内容。
现在,os.remove()在删除时失败,显示“权限被拒绝”,因为temp.txt文件仍然被系统锁定。看起来这是因为the_programm.exe还在运行(它的输出似乎也被重定向到了temp.txt)。
有没有办法在the_programm.exe运行时,不让temp.txt被锁定?Python部分几乎不能更改,因为这是一个工具(BusyB)。
实际上,我并不需要the_programm.exe的输出,所以问题的核心是:如何让the_programm.exe运行时不锁定temp.txt?或者:如何使用START或其他Windows命令启动一个程序,而不继承批处理的输出重定向?
6 个回答
你在读完文件后有关闭它吗?我这边下面的代码是可以正常工作的:
import os
os.system('runbat.bat > runbat.log 2>&1')
f = open('runbat.log')
print f.read()
f.close()
os.remove('runbat.log')
但是如果我把 f.close()
这一行去掉,就会出问题。
这个方法有点小技巧,但你可以试试。它使用了 AT
命令来在未来的一分钟内运行 the_programm.exe
(这个时间是通过 %TIME%
环境变量和 SET
算术计算出来的)。
这是一个批处理文件:batch.bat
@echo off
setlocal
:: store the current time so it does not change while parsing
set t=%time%
:: parse hour, minute, second
set h=%t:~0,2%
set m=%t:~3,2%
set s=%t:~6,2%
:: reduce strings to simple integers
if "%h:~0,1%"==" " set h=%h:~1%
if "%m:~0,1%"=="0" set m=%m:~1%
if "%s:~0,1%"=="0" set s=%s:~1%
:: choose number of seconds in the future; granularity for AT is one
:: minute, plus we need a few extra seconds for this script to run
set x=70
:: calculate hour and minute to run the program
set /a x=s + x
set /a s="x %% 60"
set /a x=m + x / 60
set /a m="x %% 60"
set /a h=h + x / 60
set /a h="h %% 24"
:: schedule the program to run
at %h%:%m% c:\the_programm.exe
你可以查看 AT /?
和 SET /?
来了解这些命令的具体作用。我没有加上 /interactive
参数,因为你提到“不能有用户交互”。
注意事项:
- 看起来
%TIME%
总是以24小时制显示时间,不管控制面板的区域设置是什么,但我没有证据证明这一点。 - 如果你的系统负担很重,导致 batch.bat 运行超过10秒,
AT
命令会被安排在1天后运行。你可以手动恢复这个任务,使用AT {job} /delete
,并把x=70
增加到一个更合适的值。
不幸的是,START
命令即使加上了 /i
来忽略当前环境,似乎还是会传递父进程 cmd.exe
的打开文件描述符。这些文件描述符会被传递给子进程,即使子进程被重定向到 NUL,并且即使中间的 shell 进程结束,这些文件描述符仍然会保持打开状态。如果你有一个批处理文件,它 START
另一个批处理文件,然后又 START
另一个(以此类推),最后 START
一个图形界面的 Windows 应用程序,你可以在 Process Explorer 中看到这个现象。一旦中间的批处理文件结束,图形应用程序将拥有这些文件句柄,即使它(和中间的批处理文件)都被重定向到 NUL
。
最后我找到了一个合适的解决办法:
我不再使用批处理文件来启动 the_programm.exe,而是用一个 Python 脚本:
from subprocess import Popen
if __name__ == '__main__':
Popen('C:/the_programm.exe', close_fds=True)
close_fds 参数可以把文件句柄和 .exe 进程分开!就这么简单!