首页 > 解决方案 > Windows批处理文件将信号发送到python脚本

问题描述

我正在使用一个 windows .bat 文件来运行两个 python 脚本,每个脚本都有处理SIGINT干净地关闭,因为它们正在写入文件。

当我执行 .bat 文件时情况并非如此,有没有办法在接收到 Ctrl+c 时将 a 发送SIGINT到 python 脚本。

有问题的 python 脚本用于测量传感器流并将数据写入 tsv 文件,我不会在这里编写整个脚本,但在功能上它相当于

import serial
import signal
import time
from datetime import datetime, timezone

def signal_handler(signal, frame):
    global interrupted
    interrupted = True


signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
interrupted = False

if __name__ == '__main__':
    serialPort = serial.Serial(port='COM4', baudrate=9600)
    serialString = ""

    t = round(time.time())
    filename = f'gps_{t}.tsv'
    print(f"Writing to {filename}")
    with open(filename, 'w') as f:
        f.write("time\tnmea\n")
        while True:
            if serialPort.in_waiting > 0:
                t = time.time()
                serialString = serialPort.readline()[:-2].decode('Ascii')
                f.write(f"{t}\t{serialString}\n")

            if interrupted:
                print("Breaking gnss loop...")
                break

BAT文件只是

START "" /B "C:\Users\nisso\anaconda3\envs\sensor_logging\python.exe" "C:/Users/nisso/Desktop/sensor_logging/gnss_reader.py"
START "" /B "C:\Users\nisso\anaconda3\envs\sensor_logging\python.exe" "C:/Users/nisso/Desktop/sensor_logging/imu_reader.py"

cmd /c exit 3221225786

我基本上只使用linux,这里的windows是必须的,因为另一个传感器带有一些专有软件,所以我对批处理文件等的了解非常有限。

标签: python-3.xbatch-file

解决方案


在这种情况下,首先通过处理批处理文件来处理由 Windows 发送到具有输入焦点的前台控制台应用程序的信号SIGINT(或SIGBREAK),并导致提示用户是否应该终止批处理作业。Ctrl+Ccmd.exe

python.exe将是进程层次结构中的下一个进程,但它不会像cmd.exe首先接收和处理它那样得到这个信号。有关CTRL+C 和 CTRL+BREAK 信号,请参阅 Microsoft 文档。

可以在%SystemRoot%\System32\taskkill.exe 使用选项的情况下将CTRL+CLOSE 信号(控制台应用程序)和WM_CLOSE 消息(GUI 应用程序)分别发送到由其进程标识符 (pid) 标识/F的特定进程。另请参阅有关注册控制处理程序函数的 Microsoft 文档。python.exe

看起来这个任务的批处理文件有几个要求:

  1. 批处理文件不应仅用于启动两个 Python 进程,这两个 Python 进程在 Python 获得终止或中断信号之前一直在运行,而且还用于在用户按下一个键时终止两个 Python 进程,例如在为处理批处理Ctrl+C而打开的控制台窗口中cmd.exe文件。

  2. 这两个 Python 脚本将信息打印到用户应该能够阅读的标准输出流。

  3. 在启动两个 Python 进程时可能已经在运行一个或多个python.exe进程,甚至可能发生另一个进程在处理批处理文件并启动两个 Python 进程时启动一个python.exe进程cmd.execmd.exe这需要特殊的代码来获取真正通过处理批处理文件启动的两个 Python 进程的进程标识符。

  4. 如果两个 Python 进程都使用cmd.exe处理批处理文件的控制台窗口将它们的输出输出到标准输出流,那将是最好的。

由 Windows 命令处理器解释的批处理文件很难满足这些要求,而 Windows 命令处理器并不是真正为此类复杂的进程管理任务而设计的。编写执行以下操作的第三个 Python 脚本会更好:

  1. 使用sys.executable获取解释第三个 Python 脚本的 Python 可执行文件的完整限定文件名。
  2. 用于os.path.realpath(__file__)获取当前执行的第三个 Python 脚本的目录的完整路径,其中还包含 Python 脚本gnss_reader.pyimu_reader.py.
  3. 使用子进程模块,在 Windows 上使用 Windows 内核库函数CreateProcess和结构STARTUPINFO打开两个子进程,分别以 Python 可执行文件名和脚本文件路径连接gnss_reader.py作为imu_reader.py参数,并使用脚本文件路径指定当前工作目录。

python.exe这使第三个 Python 脚本可以完全控制输出到从第三个 Python 脚本文件开始时打开的控制台窗口的内容。当第三个 Python 脚本文件由于来自父进程或操作系统的信号或第三个 Python 脚本提供的用于终止的用户输入而自行终止时,第三个 Python 脚本还可以完全控制如何优雅地终止两个 Python 子进程。

我不是 Python 程序员,因此没有编写 Python 解决方案。
但我可以提供两种可能的批处理文件解决方案。


第一个批处理文件使用带有Win32_Process 类的 Create 方法的WMIC以适当的 Python 脚本文件作为参数启动两个 Python 进程,并将批处理文件的目录设置为两个 Python 进程的当前工作目录。

这个批处理文件解决方案的优点是wmic返回创建的进程的信息,ProcessId其中包含可以从cmd.exe后台启动的实例的标准输出中读取,分别cmd.exe处理批处理文件以执行wmic命令行。

该解决方案的缺点是,对于每个 Python 进程,都会打开一个控制台窗口,因此该解决方案无法满足第四个要求。

@echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "usebackq skip=5 tokens=2 delims=;= " %%I in (`%SystemRoot%\System32\wbem\wmic.exe PROCESS CALL Create '"%UserProfile%\anaconda3\envs\sensor_logging\python.exe" "%UserProfile%\Desktop\sensor_logging\gnss_reader.py"'^, "%~dp0"`) do set "PythonPIDs=/PID %%I" & goto StartSecond
:StartSecond
for /F "usebackq skip=5 tokens=2 delims=;= " %%I in (`%SystemRoot%\System32\wbem\wmic.exe PROCESS CALL Create '"%UserProfile%\anaconda3\envs\sensor_logging\python.exe" "%UserProfile%\Desktop\sensor_logging\imu_reader.py"'^, "%~dp0"`) do set "PythonPIDs=%PythonPIDs% /PID %%I" & goto WaitTerminate
:WaitTerminate
echo/
%SystemRoot%\System32\choice.exe /C CE /N /M "Terminate with C or E ..."
%SystemRoot%\System32\taskkill.exe %PythonPIDs%
endlocal
pause

批处理文件的用户可以按CE甚至Ctrl+C回答提示cmd.exe以终止批处理作业N以继续批处理文件处理命令TASKKILL以终止两个已启动的 Python 进程。

该解决方案已使用我编写的控制台应用程序进行了测试,该应用程序已启动,而不是python.exe等待用户按下按键以自行终止,但被TASKKILL优雅地终止。


第二个批处理文件解决方案期望在启动批处理文件中指定的两个 Python 进程python.exe时不启动其他实例。cmd.exe批处理文件目录再次用作python.exe.

@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "PythonPIDs= "
for /F "tokens=2" %%I in ('%SystemRoot%\System32\tasklist.exe /FI "IMAGENAME eq python.exe" /NH 2^>nul') do set "PythonPIDs=!PythonPIDs!%%I "
start "" /B /D "%~dp0" "%UserProfile%\anaconda3\envs\sensor_logging\python.exe" "%UserProfile%\Desktop\sensor_logging\gnss_reader.py"
start "" /B /D "%~dp0" "%UserProfile%\anaconda3\envs\sensor_logging\python.exe" "%UserProfile%\Desktop\sensor_logging\imu_reader.py"
for /F "tokens=2" %%I in ('%SystemRoot%\System32\tasklist.exe /FI "IMAGENAME eq python.exe" /NH 2^>nul') do if "!PythonPIDs: %%I =!" == "!PythonPIDs!" (set "PythonPIDs=!PythonPIDs!%%I ") else set "PythonPIDs=!PythonPIDs: %%I = !"
set "PythonPIDs=!PythonPIDs: = /PID !"
set "PythonPIDs=!PythonPIDs:~1,-5!"
echo/
%SystemRoot%\System32\choice.exe /C CE /N /M "Terminate with C or E ..."
%SystemRoot%\System32\taskkill.exe %PythonPIDs%
endlocal
pause

此批处理文件首先定义环境变量PythonPIDs,其中包含所有正在运行的进程的进程标识符的空格分隔列表python.exe

接下来启动两个 Python 进程,其输出到标准输出流被写入在开始cmd.exe处理批处理文件时打开的控制台窗口中。

然后第二次运行TASKLIST以删除python.exe环境变量的字符串值中已经存在的所有进程标识符,PythonPIDs并添加(希望只有两个)刚刚启动的 Python 进程的进程标识符。因此,环境变量PythonPIDs最终应该使用通过批处理文件启动的两个 Python 进程的进程标识符来定义。

如果PythonPIDs在启动批处理文件时已经运行的 Python 进程之一在两个 Python 进程由cmd.exe. 对于两个启动的 Python 进程,可以使用与PythonPIDs. 但是这个解决方案在任何情况下都要求在cmd.exe启动两个 Python 进程时没有另一个进程启动任何 Python 进程。因此也可以假设在两个 Python 进程启动时没有 Python 进程终止。对于确定两个启动的 Python 进程的进程标识符,该解决方案永远不会完美。

两个启动的 Python 进程的终止以与第一个批处理文件相同的方式完成。

使用与第一个批处理文件解决方案相同的控制台应用程序在我的测试中正常终止无法正常工作,等待用户按键进行自我终止。这对我来说并不奇怪,因为控制台应用程序的两个实例使用相同的标准输入流来cmd.exe处理批处理文件并启动choice.exe。但我希望它适用于运行时不处理标准输入的 Python 脚本。

/B命令START的选项并不意味着它python.exe实际上是在后台作为单独的进程启动的,具有自己的标准输入、输出和错误流,而无需打开控制台窗口。选项的使用/B导致在不打开新控制台窗口python.exe的情况下作为子进程启动cmd.exe,但使用标准输入、输出和错误流cmd.exe。因此,带有第三个 Python 脚本的 Python 解决方案很可能是最好的解决方案。


要了解使用的命令及其工作原理,请打开命令提示符窗口,在其中执行以下命令,并仔细阅读每个命令显示的所有帮助页面。

  • call /?... 解释%~dp0... 参数 0 的驱动器和路径,它是包含批处理文件的目录的完整路径。
  • choice /?
  • echo /?
  • endlocal /?
  • for /?
  • goto /?
  • if /?
  • pause /?
  • set /?
  • setlocal /?
  • start /?
  • taskkill /?
  • tasklist /?
  • wmic /?
  • wmic process /?
  • wmic process call /?
  • wmic process call create /?

推荐阅读