在日常开发过程中,我们经常会写一些脚本来帮助我们实现部分自动化的功能。而在使用脚本的过程中,参数解析又是经常使用的功能。本文将介绍 windows 平台上 bat 脚本如何进行参数解析。
1 参数的组成
首先我们来看一下命令的组成,一个完成的命令由命令(command)、选项(option)和位置参数(position argument)组成。如 git checkout -b dev
命令中。git
为主命令(command),checkout
为子命令(sub-command),-b
为选项(option)而 dev
为位置参数(positional argument)。
- 主命令和子命令统称为命令(command),是必不可少的一部分。有些复杂的命令会将命令分为主命令和子命令两个部分,如 git 命令、ros 命令、docker 命令等。但是对于大部分简单的应用一个主命令就够了。
- 选项(option)是控制命令行为最常用的方式,一般一个选项都会有长选项和短选项两种方式。如
node -h
和node --help
。一般长命令由--
开始而短命令由-
开始。虽然 windows 风格的选项是以/
开始(如rd /s /q my_dir
),但是由于 linux 风格的选项更加通用,因此这里将使用 linux 风格的选项。有些选项可能需要指定值,选项的赋值方式一般有两种形式:-
选项 值
,如mysql --host localhost
. -
选项=值
,如mysql --host=localhost
.
-
- 位置参数(positional argument)往往是命令中的必选参数。如 git 中的切换分支命令,你必须要指定一个分支,因此这里分支名就是一个必选参数。又比如打开文件命令,文件是必选项,因此文件应该是打开文件命令的一个位置参数。但是位置参数也不一定是必选的,在一些命令中,也有可能包含一些可选的位置参数。一般情况下,位置参数会放在命令的最后。由于位置参数的使用相对来说没有那么严格,因此在一些的命令中,都没有位置参数。比如
ls
命令。位置参数必须要在前面加上任何前导符,如curl [options...] <url>
命令中,最后的url
就是位置参数。
2 示例说明
介绍完参数的基本组成之后,接下来我们就开始设计 bat 的参数解析方式了。由于我们使用 bat 脚本往往不会实现太复杂的功能。因此这里设计的参数解析也有一些简化,这里并不会使用位置参数,需要位置参数的地方,我们可以使用必须选项来代替。这里的参数解析主要实现以下功能:
- 选项包括长选项和短选项两种方式。
- 选项包括需要赋值的选项和无需赋值的选项。
- 选项参数无效的时候给出错误警告。
3 示例代码
@echo off
set virtual_env=conan
setlocal EnableDelayedExpansion
set is_workon=0
set valid_param=0
set set_vir_env=0
set set_force=0
set set_help=0
set force=0
cd ..
set pro_root=!cd!
:loop
if not "%1"=="" (
set valid_param=0
if "%1" == "--vir_env" set set_vir_env=1
if "%1" == "-v" set set_vir_env=1
if "!set_vir_env!"=="1" (
if "%2"=="" (
echo parameter not enough, virtual environment must be give.
goto :end
)
set virtual_env=%2
set valid_param=1
shift
)
if "%1" == "--force" set set_force=1
if "%1" == "-f" set set_force=1
if "!set_force!"=="1" (
set force=1
set valid_param=1
)
if "%1" == "--help" set set_help=1
if "%1" == "-h" set set_help=1
if "!set_help!" == "1" (
echo USAGE: %0 options.
echo options:
echo `--vir_env/-v value`: set conan virtual environment to the value, default is 'conan'.
echo `--force/-f`: overwrite exist package.
echo `--help/-h`: print this message and exit.
goto :end
)
if "!valid_param!" == "0" (
echo "unknown options: %1, use `-h` to show all avaliable options."
goto :end
)
shift
goto :loop
)
CALL workon !virtual_env!
set is_workon=1
if exist "conan_builds" rd /q /s "conan_builds"
mkdir conan_builds
cd conan_builds
REM build and install
cmake -DCMAKE_INSTALL_PREFIX=%pro_root%/conan_install ..
cmake --build . --config Release
ctest -VV -C Release
if errorlevel 1 (
echo "ctest failed."
goto :end
)
cmake --install . --config Release
REM package
cd !pro_root!/conan_pkg
if "!force!"=="1" (
conan export-pkg . common/dev --package-folder=!pro_root!/conan_install -f
) else (
conan export-pkg . common/dev --package-folder=!pro_root!/conan_install
)
if errorlevel 1 (
echo "package failed."
goto :end
)
REM test
cd !pro_root!/conan_pkg/pkg_test
if exist "builds" rd /q /s "builds"
mkdir builds
cd builds
conan install ..
cmake ..
cmake --build . --config Release
bin\main.exe
if errorlevel 1 (
echo "package test failed."
goto :end
)
:end
if "!is_workon!" == "1" (
CALL deactivate
)
cd !pro_root!/scripts
这个示例代码是用来实现 c++ 库的自动编译、测试、打包、测试包的流程。由于这里我们主要是学习如何使用 bat 的参数解析。因此具体的执行内容其实我们不用关心。这里一共使用了三个参数:
-
-v, --vir_env
用来指定 python 的虚拟环境名称。这是一个带值的选项。这里注意获取到值时,需要额外执行一次shift
。 -
-f, --force
如果之前已经打包过相同的包了,是否覆盖之前的包。这是一个不带值的选项。 -
-h, --help
用来显示帮助信息。
在输入其他无效的选项时,会报错。这里在实现的时候需要注意一下几点:
- 由于 bat 脚本的
if
语句中,无法实现两个条件的“或”运算,所以这里使用两个if
语句来分别判断一个选项的长短形式,同时还使用了一个中间变量保存设置结果。 - 部分变量的引用使用
!变量!
的形式,而不是%变量%
的形式。这是为了保证这些变量能够延后解析。如果使用%变量%
来引用变量,这些变量并不会随着我们的赋值而改变。同时需要注意的是!变量!
的引用形式需要声明setlocal EnableDelayedExpansion
。 - bat 脚本在执行命令的时候并不会因为命令执行出错而停止。因此我们需要通过判断
errorlevel
这个变量来判断之前的一个命令是否执行成功,如果执行失败可以选择退出脚本。
其他的语句要么比较容易理解,或者是和业务相关无需关心,因此这里不再介绍了。
参考资料:
windows batch SET inside IF not working
How do I get the application exit code from a Windows command line?
Windows Bat file optional argument parsing
How to use logical "OR" operator in batch script