batch 脚本实战之互换文件夹名

Posted by Harry on 2019-12-22
Words 1.4k and Reading Time 6 Minutes
Viewed Times

背景

今天做了一个功能,通过 batch 脚本回滚 Electron 客户端版本,需求转化为将客户端安装目录下的版本号文件夹互换名称,具体为什么就不在这里详细说明了。具体目标如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 执行 rockback.bat 前,根目录结构
|-- app-1.1.0
|-- app.exe(1.1.0)
|-- app-1.1.1
|-- app.exe(1.1.1)
|-- app.exe // 索引默认访问最新版本 app-1.1.1/app.exe

// 执行 rockback.bat 后,根目录结构
|-- app-1.1.1
|-- app.exe(1.1.0)
|-- app-1.1.0
|-- app.exe(1.1.1)
|-- app.exe

互换两个版本的文件夹名,通过这种调包的方式来使得 ./app.exe 访问到的虽然是最新版本的路径,但却实际打开的是上一个版本的可执行程序。以此来达到当客户端出现紧急事故,需要将最新版本回滚至上一版本时可以通过脚本完成一键切换。

首战未果,坑何在

对于 batch 脚本我也只是略知一二,结合网上的一些资料尝试写出了以下这第一个版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
:: 这里的命令会尝试把所有以 {app-} 开头的文件夹找出来
:: 并通过循环将文件夹名赋值给 folderName{num}
set folderNameT=temp
set /a num=0
for /f "delims=" %%i in ('dir /ad /b app-*') do (
set /a num=%num%+1
set folderName%num%=%%i
)
:: 当以 {app-} 开头的文件夹数等于 2 的时候才进行互换
:: 引入临时文件夹名 folderNameT,运用重名命令 ren 互换名称
if %num% equ 2 (
ren "%folderName1%" "%folderNameT%"
ren "%folderName2%" "%folderName1%"
ren "%folderNameT%" "%folderName2%"
echo Rockback Success!
) else (
echo Rockback fail! Not valid version to rockback.
)
pause

调试输出结果如下,发现与想要的结果有很大差别。认真查看输出,发现关键错误点在于 num 变量的值并没有随着循环的进行逐步完成赋值。num 的值在循环结束前一直都等于 0。

1
C:\Users\harry\Desktop\temp>set folderNameT=temp
C:\Users\harry\Desktop\temp>set /a num=0
C:\Users\harry\Desktop\temp>for /F "delims=" %i in ('dir /ad /b app-*') do (
    set /a num=0+1
    set folderName0=%i
)
C:\Users\harry\Desktop\temp>(
    set /a num=0+1
    set folderName0=app-ims-1
)
C:\Users\harry\Desktop\temp>(
    set /a num=0+1
    set folderName0=app-ims-2
)
C:\Users\harry\Desktop\temp>if 1 EQU 2 (
    ren "" "temp"
    ren "" ""
    ren "temp" ""
    echo Rockback Success!
)  else (echo Rockback fail! Not valid version to rockback. )
Rockback fail! Not valid version to rockback.

EnableDelayedExpansion 何许人也

一脸懵逼可干不了活,我认真对比了网上一些实现功能相似的文章,比如Windows批处理之修改文件名。花了点时间后,发现写法上的确有点不同,首先就是开头一般都有一句 setlocal EnableDelayedExpansion 的命令,而且对于 num 变量的取值也是采用 !num! 而非 %num%。于是我马上去搜索了下这个 setlocal EnableDelayedExpansion 的命令,在一篇貌似官方文档的介绍中发现了这个命令的定义:

EnableDelayedExpansion
Delayed Expansion will cause variables within a batch file to be expanded at execution time rather than at parse time, this option is turned on with the SETLOCAL EnableDelayedExpansion command.
Variable expansion means replacing a variable (e.g. %windir%) with its value C:\WINDOWS
By default expansion will happen just once, before each line is executed.
The !delayed! expansion is performed each time the line is executed, or for each loop in a FOR looping command.
For simple commands this will make no noticable difference, but with loop commands like FOR, compound or bracketed expressions delayed expansion will allow you to always see the current value of the variable.
When delayed expansion is in effect, variables can be immediately read using !variable_name! you can still read and use %variable_name% but that will continue to show the initial value (expanded at the beginning of the line).

译文意思大致如下:
EnableDelayedExpansion 将导致批处理文件中的变量在执行时才扩充到引用的地方,而不是在解析时这些代码的时候就马上被扩充,此选项可以通过 SETLOCAL EnableDelayedExpansion 命令打开。
变量扩充着用它的值 C:\ WINDOWS 替换一个变量(例如%windir%)
默认情况下,每个变量的扩充只会在每行执行之前发生一次。
!delayed! 意味着在每次执行当前行时进行扩充,或者 FOR 循环命令中的每个循环。
对于简单的命令来说,这不会有什么不同,但是对于像 FOR 这样的循环命令,复合或括号表达式的延迟扩充将始终允许读取变量的当前值。
EnableDelayedExpansion 开启时,可以在需要的时候使用 !variable_name! 读取变量当前值,你仍然可以使用 %variable_name% 来读取并使用变量,但是这种方式获取的变量值只会显示初始值(在开头处就已经被扩充)。

这么一通阅读下来,基本上已经可以明确了这个 %num%!num! 的区别,一个是初始化时就已经确定值,一个是运行到相关的命令时才获取值。而我的需求恰好就是这样的使用场景。经过后续调试验证,以下修改后的脚本已经可以达到我想要的目的了,工作也得以顺利完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@echo off
setlocal EnableDelayedExpansion
:: find app folder
set folderNameT=temp
set /a num=0
for /f "delims=" %%i in ('dir /ad /b app-*') do (
set /a num=!num!+1
set folderName!num!=%%i
)
:: swap folderName
if !num! equ 2 (
ren "!folderName1!" "!folderNameT!"
ren "!folderName2!" "!folderName1!"
ren "!folderNameT!" "!folderName2!"
echo Rockback Success!
) else (
echo Rockback fail! Not valid version to rockback.
)
pause

This is copyright.