如何在批处理文件中获取刚启动的进程的PID?

48
在Windows批处理脚本中,有一个启动新进程的start命令。是否可以获取刚启动的进程的PID?

请参考 kybernetikos 的回答frog.clemens (即我自己) 的回答,了解两种不同的方法,来获取在 DOS 中启动进程时的 PID。 - frog.ca
7个回答

39
这是一篇旧文章,但我认为值得分享以下“易于使用”的解决方案,它在Windows上现在可以很好地工作。
同时启动多个进程:
start "<window title>" <command will be executed>

例子:

start "service1" mvn clean spring-boot:run
start "service2" mvn clean spring-boot:run

获取进程PID(可选):

tasklist /V /FI "WindowTitle eq service1*"
tasklist /V /FI "WindowTitle eq service2*"

结束进程:

taskkill /FI "WindowTitle eq service1*" /T /F
taskkill /FI "WindowTitle eq service2*" /T /F

1
当我尝试执行命令 start "javapp" java -jar $app_path & 时,出现一个警告弹窗显示 系统找不到 javapp 文件。这是我做错了吗? - Zavael
这在Windows 10上运行得非常好:start "javaapp" java -version & $app_path是一个Unix变量。对于Windows,您需要使用:%app_path%。 - zappee
是的,我正在Win10上的Bash中运行它,因此使用了Unix变量...奇怪的是,当我运行start "javaapp" java -version时,我得到了相同的结果(即缺少文件javaapp警告)...两个命令都没有“javaapp”名称也可以正常工作 :( - Zavael
6
这种方法不适用于GUI应用程序(例如,“start" x "notepad"将不会导致新的控制台窗口,因此寻找WindowTitle将无法工作)。原帖并未说明是指GUI还是命令行进程。此解决方案也无法处理出现多个指定窗口标题的情况(所有“匹配”的进程都将被终止)。 - frog.ca
我使用带有窗口标题的 /B 在后台启动了该进程,但是当我尝试终止该进程时,它并没有被终止。这种方法适用于在后台运行的进程吗? - SRJ
显示剩余2条评论

18
你可以批处理,但不能直接执行。你需要解析tasklist.exe的输出或使用wmic.exe。这两个都要求你知道你刚刚启动了什么,当然你会知道。
使用tasklist.exe:
for /F "TOKENS=1,2,*" %a in ('tasklist /FI "IMAGENAME eq powershell.exe"') do set MyPID=%b
echo %MyPID%

为在批处理脚本中使用此命令,请将百分号加倍。

使用 wmic.exe:

for /f "TOKENS=1" %a in ('wmic PROCESS where "Name='powershell.exe'" get ProcessID ^| findstr [0-9]') do set MyPID=%a
echo  %MyPID%

15

如果已经有相同名称的进程在运行,您首先需要获取当前pid列表,然后启动本地进程,然后再次检查pid。以下是一个样例代码,它启动3个进程并在结束时将它们杀死(特别是启动本地进程):

@echo off
set PROCESSNAME=notepad.exe

::First save current pids with the wanted process name
setlocal EnableExtensions EnableDelayedExpansion
set "RETPIDS="
set "OLDPIDS=p"
for /f "TOKENS=1" %%a in ('wmic PROCESS where "Name='%PROCESSNAME%'" get ProcessID ^| findstr [0-9]') do (set "OLDPIDS=!OLDPIDS!%%ap")

::Spawn new process(es)
start %PROCESSNAME%
start %PROCESSNAME%
start %PROCESSNAME%

::Check and find processes missing in the old pid list
for /f "TOKENS=1" %%a in ('wmic PROCESS where "Name='%PROCESSNAME%'" get ProcessID ^| findstr [0-9]') do (
if "!OLDPIDS:p%%ap=zz!"=="%OLDPIDS%" (set "RETPIDS=/PID %%a !RETPIDS!")
)

::Kill the new threads (but no other)
taskkill %RETPIDS% /T > NUL 2>&1
endlocal

3
当然,如果您同时运行多个版本的脚本,这会引入竞争条件。在使用"taskkill"时可能会有害,因为一旦被杀死的进程ID可能会重新分配给完全不希望被"再次"终止的线程。在这种情况下,您可能不能依赖Windows批处理语法 :) - Oliver Zendel
这也没有解决进程ID重用的问题。点赞指出Windows批处理功能可能不是解决问题的足够工具。 - frog.ca

5
你可以尝试使用以下方法:
wmic process call create "notepad"

这将返回创建进程的pid。

使用FOR进行处理。

setlocal
set "ReturnValue="
set "ProcessId="
for /f "eol=} skip=5 tokens=1,2 delims=;= " %%a in ('wmic process call create "notepad"') do (
    set "%%a=%%b"
)
echo %ReturnValue%
echo %ProcessId%
endlocal

我认为在运行带有引号参数的命令时会遇到问题。 - Steve Smith
@SteveSmith 你有什么命令方面的困扰吗?引号可以通过几种方式进行转义。 - npocmaka
@SteveSmith 你有遇到什么命令有困难吗?引号可以通过几种方式进行转义。 - npocmaka
我试图运行ffplay,但输入设备名称中有空格。我找到了一种不同的方法来解决它(与需要获取pid无关)。 - Steve Smith
我试图运行ffplay,输入设备名称中有空格。我找到了另一种方法来解决这个问题(与获取pid无关)。 - Steve Smith

4

可以使用PowerShell来完成这个任务:

powershell -executionPolicy bypass -command "& {$process = start-process $args[0] -passthru -argumentlist $args[1..($args.length-1)]; exit $process.id}" notepad test.txt

echo Process ID of new process: %errorlevel%

0
这是我用来获取PID的代码。
for /f "tokens=2 delims=," %%a in ('tasklist /FO CSV ^| findstr /I /C:"entertheprocess.here"') do (
    echo PID:%%a
)

1
这个可以工作,但只有在应用程序单一实例运行时才有效。 - Steve Smith
1
这个方法有效,但只适用于单个应用程序实例运行的情况。 - Steve Smith

-1
@ECHO OFF
SETLOCAL EnableDelayedExpansion EnableExtensions
::
::weil es mehrere Sessions für UltraCompare gleichzeitig geben kann, es wird hier die
::neueste Instanz (soeben gestartet) ermittelt, um später mit ProcessId diese Instanz 
::aktivieren zu können...
::
set "CreationDate="
set /A "ProcessIdUltraCompare=0"
::
set /A "lineno=0"
FOR /F %%T IN ('Wmic process where^(Name^="uc.exe"^) get CreationDate^|sort /r') DO (
set /A lineno+=1
rem echo %%T
rem echo !lineno!
if !lineno! equ 2 (
    rem ECHO %%T   
    set CreationDate=%%T
    rem echo !CreationDate!
    set /A "lineno=0"
    FOR /F %%P IN ('Wmic process where^(CreationDate^="!CreationDate!"^) get ProcessId') DO (
          set /A lineno+=1
          rem echo %%P
          if !lineno! equ 2 (
              set "ProcessIdUltraCompare=%%P"
              rem echo ProcessIdUltraCompare=!ProcessIdUltraCompare!
              goto :l_pid_uc_got
          )
      )
 )
)  
:l_pid_uc_got
    echo ProcessIdUltraCompare=!ProcessIdUltraCompare!
PAUSE

没有对投票下降的解释,但当然这会引起“应用程序实例刚刚启动”的定义的竞争条件,因为多个实例可能“刚刚启动”,按时间戳进行操作可能会误导/错误。我建议采用上面展示的基于PowerShell的方法来避免这种不确定性。 - frog.ca

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接