Windows服务运维脚本指南

我在写go服务程序,而且运行在Windows上,为了方便启停服务,我决定写一个脚本来管理服务。由于运行在Windows上,所以选择bat批处理脚本。

首先我们需要确定一下脚本的结构。我们需要4个参数,用来启动、重启、停止应用以及查看应用状态,目标如下:

  • run.bat start 启动应用
  • run.bat restart 重启应用
  • run.bat stop 停止应用
  • run.bat status 查看应用状态

OK,非常简单,我们通过 if 来实现这些命令:

@echo offif "%1" == "start" (echo starting server....
) ^
else if "%1" == "restart" (echo restarting server...
) ^
else if "%1" == "stop" (echo stopping server...
) ^
else if "%1" == "status" (echo server status...
) ^
else (echo "USAGE: run.bat [start|restart|stop|status]"
)

这就是我们的第一个版本,只是打印出一下信息,并无实际功能,不用担心,我们稍后会一步步把所有功能都添加进来。

现在先让我们来看看上面的脚本,首先第一行通过 @echo off 关闭命令回显,常规操作。注意在 if 的条件那里我们给等号两边的表达式都加上了双引号,这是必须的,如果你写成 if %1 == start 将不会有任何一个分支能匹配上。当然,除了双引号,你也可以使用方括号,写成 if [%1] == [start] ,这样也能正确匹配。不过我更喜欢双引号。至于 ^ 只是为了能够换行书写 else 分支,让脚本更好看一点而已。

在最后一个 else 分支我们通过 echo 打印命令用法,这里的双引号也是必须的,因为据我尝试,在bat脚本中,方括号似乎有着某种神奇魔力。

这四个功能并不复杂,相信你已经迫不及待想要去实现他们了。但是,请等一等,我们希望这个脚本能更“好”。

举几个例子,比如我们希望在启动应用之前能先判断应用是否正在运行,而不是直接运行程序。再比如再重启时,先判断应用是否正在运行,是的话先停止它,然后再启动应用。停止应用时也可以做类似的判断。所有我们要先解决一个问题:如何判断程序的运行状态?

我的服务在启动后会将进程ID写入一个叫 pid 的文件中,所以我就直接用这个文件了。当然,获取进程PID并写入文件也可以通过脚本来实现,只是我的服务已经写入PID了,所以我的脚本就直接读取它了,感兴趣的朋友可以尝试自己实现通过脚本维护PID。

好了,废话讲完了,接下来要动真格了。

首先我需要读取PID文件中记录的进程ID,Windows里面有 type 命令可以用来读取文件内容,但是我们不能直接写 type pid ,因为它会直接将结果输出到控制台,我们无法获取到命令的结果。在bat脚本中要获取命令的结果只能用 for 指令,虽然有点离谱,但几经搜索也只有这一个办法。

获取PID的脚本如下:

for /F %%i in ('type pid') do (set pid=%%i)

我们稍微解释一下上面的命令。 in 后面的括号中的是要执行的命令,多条命名通过逗号分隔。这里是 type pid ,也就是读取pid文件。 %%i 是迭代变量,也就是每条命令的执行结果。 do 后面的括号是每次迭代所要执行的命令,这里我们将迭代结果赋值给 pid 变量。要格外注意的是 for 后面的 /F 指令,它也是必须的,在 /F 的加持下, in 后面的括号中的内容才会被当做命令执行,否则的话,括号中的内容会被拆分为token去迭代。比如,这里如果不加 /F ,那么上面的脚本会循环两次,第一次 %%i 的值是 'type ,第二次 %%i 的值是 pid'

OK,有了pid,接下来我们就可以通过 tasklist 命令来查询进程了。命令如下:

tasklist /fi "PID eq 12345"

/fi 是过滤选项,过滤出指定PID的进程,有关该命令的详细参数可以参考微软官方文档。 tasklist 输出如下:


映像名称                       PID 会话名              会话#       内存使用
========================= ======== ================ =========== ============
platform.exe                  4684 Console                    1     17,092 K

毫无疑问我们任然需要用到 for 指令来提取关键信息进行比较, /F 选项不仅可以用来执行指令,还可以用来对结果进行过滤,这里我们希望提取第4行(注意第一行有空行),第二列的内容,也就是进程ID,和我们之前从pid文件中获取的进程id进行比较,来判断进程是否存在。

for /F "skip=3 tokens=2" %%i in ('tasklist /fi "PID eq %pid%"') do (if %%i==%pid% set isrunning=yes
)

注意 /F 后面多了这样一段内容 "skip=3 tokens=2"skip 表示跳过前多少行, tokens 表示取第几列。因为我们的目标是第4行,所在跳过前3行。这里我们只取第2列,如果你想把进程名称也提取出来做一个比较,可以写成 tokens=1-2 或者 tokens=1,2 ,但是注意,这样的话 do 后面的 %%i 就变成了进程名,而进程id需要用 %%j 来访问,这是两列的情况,如果还有第三列,那么就是 %%k ,依次类推。关于bat中 for 指令的详细语法,可以自行搜索学习,这里就不再深入了。

很好,现在我们可以通过 %isrunning% 来判断进程是否存在了,让我们将他们添加到脚本中。

@echo off:: read server pid
for /F %%i in ('type pid') do (set pid=%%i)
:: check process status
for /F "skip=3 tokens=2" %%i in ('tasklist /fi "PID eq %pid%"') do (if %%i==%pid% set isrunning=yes
)if "%1" == "start" (if "%isrunning%" == "yes" (echo server is running) else (echo starting server...echo do-starting)exit /b 0
) ^
else if "%1" == "restart" (if "%isrunning%" == "yes" (echo stopping server...taskkill /f /pid %pid%)echo restarting server...echo do-startingexit /b 0
) ^
else if "%1" == "stop" (if "%isrunning%" == "yes" (echo stopping server...taskkill /f /pid %pid%) else (echo server isnot running)exit /b 0
) ^
else if "%1" == "status" (echo server status...if "%isrunning%" == "yes" (echo running) else (echo gone)exit /b 0
) ^
else (echo "USAGE: run.bat [start|restart|stop|status]"exit /b 0
)

增加了不少内容,停止应用和查看状态已经完成了,但是启动应用还没做,只是用 echo do-starting 替代了。你可能会疑惑,启动应用难道不是最简单的功能吗?只需要调用下程序就可以了。

但问题是我们希望程序能在独立的进程中运行,并且不带命令行窗口,不能随着命令行的关闭而结束,因为我的程序是一个网络服务,它需要一直运行。这在Linux中的确很容易,但是在Windows中却不那么直接。

要在Windows中实现这一点我么需要使用powershell脚本的 Start-Process 函数。详细说明可参考官方文档。命令如下:

powershell.exe -command "& {Start-Process -WindowStyle Hidden -WorkingDirectory '%~dp0' -FilePath 'platform.exe' -RedirectStandardOutput logs\stdout.log}"

-WindowStyle Hidden 选项让我们可以在不显示命令行窗口的情况下启动进程,这正是我们想要的。 -FilePath 是指定需要运行的程序。其他参数也都是字面含义。需要注意的是 -RedirectStandardError-RedirectStandardOutput 不能指向同一个文件,这里我只重定向了标准输出,如果你需要同时重定向标准输出和标准错误,需要注意这一点。

因为启动和重启应用都需要用到这段代码,并且应用启动后我们还需要再次检查进程状态,因为程序不一定能启动成功。所以我将真正启动程序以及检查状态的代码提取到了脚本的最后,而在 if 那里通过 goto 指令跳转到启动程序的地方即可。还有一点就是程序名称可以放到一个变量里,这样方便我们修改。

在脚本的最后添加下面的代码:

:do-starting
powershell.exe -command "& {Start-Process -WindowStyle Hidden -WorkingDirectory '%~dp0' -FilePath '%server%' -RedirectStandardOutput logs\stdout.log}"
timeout /T 1 /NOBREAK > nul
for /F %%i in ('type pid') do (set pid=%%i)
for /F "skip=3 tokens=2" %%i in ('tasklist /fi "PID eq %pid%"') do (if %%i==%pid% set isrunning=yes
)
if "%isrunning%" == "yes" (echo success) else (echo failed)
exit /b 0

注意在启动程序之后,我使用 timeout 命令延时了1秒钟才去检查进程状态,目的是留出足够的时间让程序启动并更新pid文件,如果你的程序启动很慢,可以适当延长这里的延时。最后别忘了将所有 echo do-starting 替换成 goto do-starting

大功告成,最终我们的脚本长这样:

@echo offset server=platform.exe
:: read server pid
for /F %%i in ('type pid') do (set pid=%%i)
:: check process status
for /F "skip=3 tokens=2" %%i in ('tasklist /fi "PID eq %pid%"') do (if %%i==%pid% set isrunning=yes
)if "%1" == "start" (if "%isrunning%" == "yes" (echo server is running) else (echo starting server...goto do-starting)exit /b 0
) ^
else if "%1" == "restart" (if "%isrunning%" == "yes" (echo stopping server...taskkill /f /pid %pid%)echo restarting server...goto do-startingexit /b 0
) ^
else if "%1" == "stop" (if "%isrunning%" == "yes" (echo stopping server...taskkill /f /pid %pid%) else (echo server isnot running)exit /b 0
) ^
else if "%1" == "status" (echo server status...if "%isrunning%" == "yes" (echo running) else (echo gone)exit /b 0
) ^
else (echo "USAGE: run.bat [start|restart|stop|status]"exit /b 0
)
exit:do-starting
powershell.exe -command "& {Start-Process -WindowStyle Hidden -WorkingDirectory '%~dp0' -FilePath '%server%' -RedirectStandardOutput logs\stdout.log}"
timeout /T 1 /NOBREAK > nul
for /F %%i in ('type pid') do (set pid=%%i)
for /F "skip=3 tokens=2" %%i in ('tasklist /fi "PID eq %pid%"') do (if %%i==%pid% set isrunning=yes
)
if "%isrunning%" == "yes" (echo success) else (echo failed)
exit /b 0

好了,预期功能都完成了,来唠点闲嗑。

首先是关于注释的,在bat中,有两种注释 ::@REM ,我发现在 if 的括号内部,不能使用 :: 这种注释,只能使用 @REM 注释。这个问题在调试的时候困扰了我好久。

其次是关于如何在Windows中不带窗口的在后台启动进程的。上面用powershell的 Start-Process 函数只是其中一种方案,因为逻辑比较直接,所以选择了这个方案。

另一种方案是使用vbs脚本来启动进程。有一种是写两个脚本,用vbs脚本去运行bat脚本。写两个脚本毕竟是不方便的,当然也有写道一个脚本里的方法。这里我也给出这个版本的写法:

@echo offif "%2" == "" goto begin:hide
if "%2" == "h" goto %1
mshta vbscript:CreateObject("WScript.Shell").Run("%~0 starting h",0)(window.close)&&exit
:beginset server=platform.exe
:: read server pid
for /F %%i in ('type pid') do (set pid=%%i)
:: check process status
for /F "skip=3 tokens=2" %%i in ('tasklist /fi "PID eq %pid%"') do (if %%i==%pid% set isrunning=yes
)if "%1" == "start" (if "%isrunning%" == "yes" (echo server is runningexit /b 0)echo starting server...goto hideexit /b 0
) ^
else if "%1" == "restart" (if "%isrunning%" == "yes" (echo stopping server...taskkill /f /pid %pid%)echo restarting server...goto hideexit /b 0
) ^
else if "%1" == "stop" (if "%isrunning%" == "yes" (echo stopping server...taskkill /f /pid %pid%) else (echo server isnot running)exit /b 0
) ^
else if "%1" == "status" (echo server status...if "%isrunning%" == "yes" (echo running) else (echo gone)exit /b 0
) ^
else (echo "USAGE: run.bat [start|restart|stop|status]"exit /b 0
)
exit:starting
"%server%"

大体架构差不多,但是逻辑却有点绕,这里稍作解释。

首先是正常调用,只有一个参数,此时会跳过vbs脚本直接到达 :begin 开始执行,对于停止和查看状态来说并无区别。

如果 start 需要启动程序,那么就会跳转到 :hide 开始执行,此时 if 条件并不满足,于是开始执行vbscript脚本。创建一个 WScript.Shell 对象并运行一个命令,关键就在于它运行的这个命令。

%~0 表示扩展命令的第一个参数,实际上就是 run.bat ,也就是vbscript实际上运行的是 run.bat starting h ,这不就是bat脚本本身吗?

你猜的没错,我们的bat脚本调用vsscript又运行了它自己,就是这么神奇,只不过这次的参数被vbscript改写了,于是进入到紧跟 :hide 的那个 if 分支,跳转到了最后的 :starting 标签,运行我们的程序。注意 Run 函数的第二个参数 0 ,表示不显示窗口。

最后还有一点忠告是不要使用 start 命令来启动程序,否则一定会有一个窗口出现,这绝对是一个坑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/32119.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面向对象的进阶---static

1.static 静态变量 package com.itheima.a01staticdemo01;public class Student {private String name;private int age;public static String teacherName;public Student() {}public Student(String name, int age) {this.name name;this.age age;}/*** 获取* return n…

FPGA 690T 高速存储设计

高速存储设计会有各种需求的考虑,那么对应的方案也不完全相同,这篇文章出一期纯FPGA实现的高速存储方案。用纯fpga实现高速存储板卡有易国产化,功耗低和体积小等特点,缺点就是灵活性不是很强,实现标准ext4和nfs文件系统…

让你的 Python 代码更快的小技巧

我们经常听到 “Python 太慢了”,“Python 性能不行”这样的观点。但是,只要掌握一些编程技巧,就能大幅提升 Python 的运行速度。 今天就让我们一起来看下让 Python 性能更高的 9 个小技巧 python学习资料分享(无偿)…

单片机练习题3

一、填空 1.与汇编语言相比, C51语言具有 、 、 、 等优点。答:可读性好,可移植性好,模块化开发与资源共享,生成的代码效率高 2.C51语言头文件包括的内容有…

java:JWT的简单例子

【pom.xml】 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.3.12.RELEASE</version> </dependency> <dependency><groupId>org.springf…

Python机器学习分类算法(三)-- 随机森林(Random Forest)

随机森林&#xff08;Random Forest&#xff09;原理基于集成学习思想&#xff0c;通过构建多棵决策树并集成它们的预测结果来提高模型的准确性和稳定性。具体来说&#xff0c;随机森林首先通过自助法&#xff08;bootstrap&#xff09;从原始数据集中随机抽取多个样本子集&…

U-Net for Image Segmentation

1.Unet for Image Segmentation 笔记来源&#xff1a;使用Pytorch搭建U-Net网络并基于DRIVE数据集训练(语义分割) 1.1 DoubleConv (Conv2dBatchNorm2dReLU) import torch import torch.nn as nn import torch.nn.functional as F# nn.Sequential 按照类定义的顺序去执行模型&…

安卓开发使用proxyman监控真机

1、真机跟电脑连接到同个网络中 2、手机里面设置代理&#xff0c;代理地址为proxyman上面指示的地址。 3、一般情况下&#xff0c;电脑的对应的端口是没开放的。需要到防火墙里面新建规则。入站规则 选择端口输入上方端口号 这样就能监控到了

计算机系统基础实训六-ShellLab实验

实验目的与要求 1、让学生更加理解进程控制的概念和具体操作方法&#xff1b; 2、让学生更加理解信号的概念和具体使用方法&#xff1b; 3、让学生更加理解Unix shell程序的原理和实现方法&#xff1b; 实验原理与内容 shell是一种交互式的命令行解释器&#xff0c;能代表…

Apple - Cryptographic Services Guide

本文翻译自&#xff1a;Cryptographic Services Guide&#xff08;更新时间&#xff1a;2018-06-04 https://developer.apple.com/library/archive/documentation/Security/Conceptual/cryptoservices/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011172 文章目录…

jnp.linalg.svd

jnp.linalg.svd 是 JAX 库中的一个函数&#xff0c;用于计算矩阵的奇异值分解 (SVD)。SVD 将一个矩阵分解成三个矩阵的乘积&#xff0c;通常表示为 A U * S * V^T&#xff0c;其中&#xff1a; A 是原始矩阵。U 是一个正交矩阵&#xff0c;列是左奇异向量。S 是一个对角矩阵&…

香橙派 5 PLUS 安装微信(arm架构、Ubuntu系统)

先上百度网盘链接&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/14I4vPYdzPLSvyJ7MR_KOgg?pwdswcz 提取码&#xff1a;swcz 里面是我们要安装的微信文件&#xff0c;文件名&#xff1a;com.tencent.WeChat-aarch64.flatpak&#xff0c;请下载。 要在Ubuntu中安装微…

Butter Knife 8

// 部分代码省略… Override public View getView(int position, View view, ViewGroup parent) { ViewHolder holder; if (view ! null) { holder (ViewHolder) view.getTag(); } else { view inflater.inflate(R.layout.testlayout, parent, false); holder new ViewHolde…

tmux的使用

简单的介绍 常用到的了解会话和窗口的区别?如何为session创建window呢?如何为window改名字呢?常用到的 查看已创建会话:tmux ls创建新的会话: tmux new -s 名字离开当前会话:tmux detach(注意是离开,并不是杀死哦!)进入指定会话:tmux attach -t 名字 或者是tmux a -…

大二C++期末复习(自用)

一、类 1.定义成员函数 输入年份判断是否是闰年&#xff0c;若是输出年份&#xff1b;若不是&#xff0c;输出NO #include<iostream> #include<cstring> using namespace std; class TDate{private:int month;int day;int year;public:TDate(int y,int m,int d)…

电路仿真实战设计教程--平均电流控制原理与仿真实战教程

1.平均电流控制原理: 平均电流控制的方块图如下,其由外电路电压误差放大器作电压调整器产生电感电流命令信号,再利用电感电流与电流信号的误差经过一个电流误差放大器产生PWM所需的控制电压,最后由控制电压与三角波比较生成开关管的驱动信号。 2.电流环设计: 根据状态平…

外部存储器

外部存储器是主存的后援设备&#xff0c;也叫做辅助存储器&#xff0c;简称外存或辅存。 它的特点是容量大、速度慢、价格低&#xff0c;可以脱机保存信息&#xff0c;属于非易失性存储器。 外存主要有&#xff1a;光盘、磁带、磁盘&#xff1b;磁盘和磁带都属于磁表面存储器…

人工智能领域的机器学习方法给我们的带来了哪些好处?

关于人工智能领域中的机器学习&#xff0c;这是一个深入且广泛的主题。以下是对该领域的简要概述&#xff0c;以及对其主要特点和发展的详细分析&#xff1a; 一、定义与概述 人工智能机器学习&#xff08;AI & ML&#xff09;是通过一定算法和数学模型&#xff0c;使计算…

【Java毕业设计】基于JavaWeb的服务出租系统

本科毕业设计论文 题目&#xff1a;房屋交易平台设计与实现 系 别&#xff1a; XX系&#xff08;全称&#xff09; 专 业&#xff1a; 软件工程 班 级&#xff1a; 软件工程15201 学生姓名&#xff1a; 学生学号&#xff1a; 指导教师&#xff1a; 导师1 导师2 文章目录 摘…

从零对Transformer的理解(台大李宏毅)

Self-attention layer自注意力 对比与传统cnn和rnn&#xff0c;都是需要t-1时刻的状态然后得到t时刻的状态。我不知道这样理解对不对&#xff0c;反正从代码上看我是这么认为的。而transformer的子注意力机制是在同一时刻产生。意思就是输入一个时间序列&#xff0c;在计算完权…