Windows bat 查找文件被哪个进程占用,并终止该进程

一、背景

我有个批处理脚本如下:

@echo off
chcp 936 & cls
cd /D F:\Chen\python3\ExciseC
set fdate=%date:~0,4%%date:~5,2%%date:~8,2%
python main.py >> crawl_record_%fdate%.log 2>&1
for /F %%f in ('dir crawl_record_*.log /B ^| find /V "%fdate%"') do move %%f archived\logs

其中,for /F语句是将除当天之外的log文件,移动到archived\logs,然后这个批处理是在任务计划中定期跑的。
运行一段时间后,偶尔发现仍有当天之外的log文件未被移动到archived\logs,后来定位发现,文件是被python.exe程序给占用了,推测可能是python main.py >> crawl_record_%fdate%.log 2>&1这个条语句在执行过程中非正常结束(如重启、断网等),导致重定向符后的文件被锁定,然后第2天,这个文件无法正常移动。

然后我的需求出现了:找到占用这个文件的进程,然后杀死它,释放被占用的文件,再进行移动操作该文件。(这些步骤都要在批处理内完成,而非图形界面操作)

二、解决问题

1. 查找占用指定文件的进程pid

这里使用handle.exe命令程序,handle.exeSysinternals Suite中的进程实用程序。
它可用于显示系统中任何进程的打开句柄的信息。可以使用它来查看打开了文件的程序,或者查看程序的所有句柄的对象类型和名称。
帮助文档:https://learn.microsoft.com/en-us/sysinternals/downloads/handle
下载地址:https://download.sysinternals.com/files/Handle.zip
下载并解压出 handle.exe / handle64.exe 应用程序,然后把它放到path环境变量里的某个路径下。

handle /v name /nobanner

/v 使输出结果为逗号分隔符的csv格式,name 要搜索的被进程打开的文件名称(接受片段),/nobanner 不显示启动banner和版权信息。更多信息请查看handle /?帮助信息。

输出结果结构如下所示:

C:\Users\cyinl>handle /v /nobanner crawl_record_20230722.log
Process,PID,Type,Handle,Name
python.exe,23156,File,0x000001E0,F:\test\crawl_record_20230722.log

注意:
tasklist也可以查看进程pid,但是它只能按照 exe/dll 模块名来查找,命令格式:
tasklist /M 模块名,像示例中的crawl_record_20230722.log这种文件,是查不到相关进程的。

2. 终止进程

handle.exe可以终止进程,格式:

handle /nobanner /p PID /c <句柄号>

但是,句柄号不太好拿到,如下:

C:\Users\cyinl>handle /nobanner /p 23156
   40: File          D:\Chen\MySoft\Python\Python3.7.7
  1D0: File          C:\Windows\System32\zh-CN\KernelBase.dll.mui
  1E0: File          F:\test\crawl_record_20230722.log
  208: File          C:\Windows\System32\zh-CN\kernel32.dll.mui

第1行的40,应该就是要关闭的进程文件的句柄号,但是能确保要关闭的就是第1行的句柄号???有些困惑。

taskkill命令也可以终止进程,格式:

taskkill /f /pid <进程号>

/f表示强制终止进程,/pid指定要终止进程的pid进程号
输出结果如下示例:

C:\Users\cyinl>taskkill /f /pid 23156
成功: 已终止 PID 为 23156 的进程。

3. 实现代码

handletaskkill两个命令,改进批处理,来解决问题了。具体实现如下:

@echo off
chcp 936 & cls
cd /D F:\Chen\python3\ExciseC
set fdate=%date:~0,4%%date:~5,2%%date:~8,2%
python main.py >> crawl_record_%fdate%.log 2>&1
for /F %%f in ('dir crawl_record_*.log /B ^| find /V "%fdate%"') do move %%f archived\logs

REM After the above command is executed, if the files are successfully moved, they will not enter the loop of this command
for /F %%f in ('dir crawl_record_*.log /B ^| find /V "%fdate%"') do (
	echo the file to move is locked: %%f
	for /F "skip=1 tokens=2 delims=," %%p in ('handle /v %%f /nobanner') do taskkill /F /PID %%p
	REM If the move command is executed immediately after the process is deleted, it will fail. Therefore, a 5-second delay is given here
	timeout /T 5 /nobreak > nul
	move %%f archived\logs
)

解释:

  1. 如果第1个for /F能正常移动文件的话,就不会进入第2个for /F
  2. 第2个for /F用来查找被占用的log文件,嵌套的第3个for /F获取进程pid,其中skip表示跳过第1行,delims=,表示使用,将数据按列分隔,tokens=2表示取第2列。然后do结构体内使用taskkill命令将查到的pid对应的进程终止掉。
  3. 测试发现,终止进程后,不能立刻去移动文件,否则可能会失败,因此这里用timeout命令给了5秒延时,然后再去移动文件。

三、拓展

手动查找并终止进程

1. 使用系统自带的资源监视器

1)在任务栏搜索资源监视器,切换到CPU选项卡,在关联的句柄-搜索句柄输入框中,输入被占用的文件名称;
2)在搜索结果中,选中需要终止的进程,右键,选择终止进程即可
手动终止进程

2. 使用Process Explorer工具

你也可以使用 Process Explorer ,它是基于handle的GUI版本,Process Explorer可查看有关哪些句柄和DLL进程已打开或加载的信息。
下载地址:https://download.sysinternals.com/files/ProcessExplorer.zip
解压后直接打开procexp.exe/procexp64.exe即可使用。
1)点击搜索图标,输入待查找的文件名称;
2)在搜索结果中,点击搜索结果,会自动定位到相关进程以及文件句柄;
3)右键对应的进程-kill Process 或者 右键对应的文件句柄-close Handle

终止进程或关闭句柄

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小青龍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值