Batch

云云:batch脚本已经死了
怎么说呢,batch脚本确实比较难用,教程残缺不全,毕竟是这是上古时代的遗物,和DOS系统有着非常密切的关系。众所周知,windows有着其他系统不具备的强大兼容性,同时这也让windows系统背上了沉重的历史包袱。(windows是不是读该成win DOS[doge])
因此,batch有着DOS时代的眼泪(悲),不过我们就像一个优雅的爵士一样,我们可以把他装裱起来放在柜台上,时时把玩欣赏,同时我们可以感受到最质朴的脚本语言之一的样子。
给想学习bat脚本的人推荐一个网站:
bat脚本之家
(链接以php结尾,php也可能是时代的眼泪吧)
一点点的罗列知识点是过于乏味的,我想通过一个个的尝试来加深对这个事物的认知。所有看到本文的人都能够一起动手尝试。
环境和工具:Windows操作系统。

官方资料: CMD文档

Try1 Hello, World!

梦开始的地方

首先,创建一个txt文件(最好用英文路径,不过windows中文支持挺好的?),在里面复制如下代码。

@echo off
echo Hello, world!
pause

然后,将txt的后缀改成bat,鼠标双击运行可以看到

Hello, world!
请按任意键继续. . .

查阅资料思考问题:

  • @echo off删去后会怎样?
  • windows和Linux的echo有什么区别?
  • pause有什么作用?

Try2 一个编译运行脚本

在windows系统中,有很多的IDE(Integrated Development Environment,集成开发环境)。比如我们在编写C++程序的时候,集成开发环境比如说Jetbrains家的Clion,微软的Visual Studio,或者简陋一点的Dev C++,它们都提供了一键编译的GUI。另外,虽然Vscode并不是IDE,不过内置的插件可以帮你完成这些步骤。但是如果我们回归到最朴素的样子,这里有一个编辑工具(比方说,gVim,Vscode,甚至是记事本,这里笔者用的Vscode),并且有某种语言的编译器,或者说解释器,我们要在这个基础上编写一个功能脚本,实现一系列的操作帮我们减少重复劳动,提高工作效率。

查阅资料思考问题:

  • “环境变量”是什么意思,它的本质是什么?
  • 如何添加环境变量?(Linux添加环境变量和Windows添加环境变量)
  • 当前工作目录是什么?
  • C程序和Python中的相对路径是以什么为基准?

假设文件目录是这样的

./
../
run.bat
C++/
    a.exe
    in.txt
    ok.cpp
    out.txt
    std_out.txt

笔者喜欢打codeforces所以编写了下面的脚本。
下面是一段脚本,它首先切换到源代码所在目录C++/编译运行filename中设置的源代码文件,编译运行得到从in.txt中读入数据,运行输出到out.txt,并且和答案的std_out.txt进行比较,如果编译失败或者是答案错误会输出FAIL,否则输出OK。这个脚本还支持两个选项,-i忽略和答案的比较,-n取消编译,只运行。 运行这个脚本需要将编译设置到环境变量,Windows一般是MinGW。

说教是无力的,自主查阅资料的过程收获颇丰

一些需要的知识:

  • bat脚本中,cd,set等命令的含义和用法。
  • 如何获取传给脚本的参数?
  • 如何获取上一条命令的返回值?
  • 标准输入输出重定向为文件
  • bat脚本字符的处理。
@echo off
cd C++

set filename=ok.cpp
set date=%DATE:~0,10%
set time=%TIME:~0,8%
echo System time %date% at %time% 

for /f "tokens=1-3 delims=/- " %%i in ("%date%")do set/a y=%%i,m=%%j,d=%%k
set date=%y%/%m%/%d%

if "%1" neq "-n" (
    echo ***start to complile***
    
    g++ -Wall %filename% -o a.exe -O2
    
    for /f "tokens=1,2 delims= " %%a in ('forfiles /m a.exe /c "cmd /c echo @fdate @ftime"') do (
        if "%%a" neq "%date%" (
            echo ***fail to complie***
            goto ERROR
        )
        if "%%b" lss "%time%" (
            echo ***fail to complie***
            goto ERROR
        )
    )
    echo ***complied successfully***
)
echo ***begin to run***
a <in.txt> out.txt

echo ***finsih running***

if "%1" == "-i" (
    goto YES
)

fc /w out.txt std_out.txt

if %Errorlevel% == 0 (
:YES
    echo   ___  _   __
    echo  / _ \^| ^| / /
    echo ^| ^| ^| ^| ^|/ / 
    echo ^| ^| ^| ^|   ^<  
    echo ^| ^|_^| ^| ^|\ \ 
    echo  \___/^|_^| \_\
if "%1" == "-i" (
    goto EOF
)
) else (
:ERROR
echo  ______      _____ _      
echo ^|  ____/\   ^|_   _^| ^|     
echo ^| ^|__ /  \    ^| ^| ^| ^|     
echo ^|  __/ /\ \   ^| ^| ^| ^|     
echo ^| ^| / ____ \ _^| ^|_^| ^|____ 
echo ^|_^|/_/    \_\_____^|______^|
                           
                          
)
:EOF

运行的效果类似

System time 2024/06/03 at 21:24:45
***start to complile***
***complied successfully***
***begin to run***
***finsih running***
正在比较文件 out.txt 和 STD_OUT.TXT
FC: 找不到差异

  ___  _   __
 / _ \| | / /
| | | | |/ / 
| | | |   <  
| |_| | |\ \ 
 \___/|_| \_\

回到正文,笔者在尝试的时候遇到很多问题,看似简单的脚本。读者可以自己去尝试的写写类似的脚本试试。在使用g++编译的时候,笔者发现,就算是编译失败后,终端获取的命令的返回值仍然是0,所以只能比较当前终端开始的时间和文件上次更新的时间,因为当g++编译失败的时候不会去更新已经编译的exe文件。windows系统本身的以GUI为主,所以脚本命令相对Linux而言功能弱一些,但是提供的API也是很多的,功能也足够强大。注意了,一个零碎的小知识点,类似Linux的man命令可以查看各个选项(在windows里面不叫“选项”叫“开关”),比如,我们查看set的选项可以在cmd窗口内键入set /?,即可查看相关选项的含义了,这个应该是DOS系统留下来的传统,之前笔者在摆弄DOSBOX的DEBUG工具的时候也是类似的。读者可能觉得这样编译不麻烦吗?其实也不麻烦,把Vscode设置成文件自动保存(不然编译的是旧文件)后,按Ctrl + ` 就能打开终端,一般是powershell,键入./run.bat,如果有历史记录的话可按方向上键,回车即可运行。

那个OK字符是我用网站生成的,有需要的可以用
字符画生成网站

推荐查阅:

  • forfiles的使用方法
  • for的使用方法

留作读者思考:

  • 使用Windows原生的Command Prompt(CMD)和Powershell运行bat脚本有什么区别?
  • 为什么笔者没有选择打印彩色字,而是使用字符画打印。
  • echo里面的^符号的作用是什么呢?

2024/6/8日修正:上述代码存在一定的问题。(っ °Д °;)っ
读者发现了吗?

set \a d=%%kset \a d=%%j均可能出现问题。
研究一下set \a使用命令set \?我们可以看到下面这段话。

/A 命令行开关指定等号右边的字符串为被评估的数字表达式。该表达式
评估器很简单并以递减的优先权顺序支持下列操作:

    ()                  - 分组
    ! ~ -               - 一元运算符
    * / %               - 算数运算符
    + -                 - 算数运算符
    << >>               - 逻辑移位
    &                   - 按位“与”
    ^                   - 按位“异”
    |                   - 按位“或”
    = *= /= %= += -=    - 赋值
      &= ^= |= <<= >>=
    ,                   - 表达式分隔符

如果你使用任何逻辑或取余操作符, 你需要将表达式字符串用
引号扩起来。在表达式中的任何非数字字符串键作为环境变量
名称,这些环境变量名称的值已在使用前转换成数字。如果指定
了一个环境变量名称,但未在当前环境中定义,那么值将被定为
零。这使你可以使用环境变量值做计算而不用键入那些 % 符号
来得到它们的值。如果 SET /A 在命令脚本外的命令行执行的,
那么它显示该表达式的最后值。该分配的操作符在分配的操作符
左边需要一个环境变量名称。除十六进制有 0x 前缀,八进制
有 0 前缀的,数字值为十进位数字。因此,0x12 与 18 和 022
相同。请注意八进制公式可能很容易搞混: 08 和 09 是无效的数字,
因为 8 和 9 不是有效的八进制位数。

注意到08和09是非法的赋值,所以上述代码再每月的8号,9号或者每年的8月,9月会出现bug,出现如下报错。

无效数字。数字常数只能是十进制(17),十六位进制(0x11)或
八进制(021)。

解决方案


2024/6/12补充: 将这个脚本移动到同学的电脑上发现问题,他电脑上没办法和我一样运行这个脚本。问题的原因是%DATE%获取的时间格式不同。据说,他调整了windows系统时间。也可能%DATE%获取系统时间的兼容性有问题。

生活不只有用之用,无用之用亦为美