代码存在多个小问题,我在我对批处理文件的建议下逐一解释这些问题。
任务要获得UNITY_FOLDER
根据UNITY_VERSION
如文件中定义的ProjectVersion.txt
使用以下代码可以更有效地完成:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
if not defined WORKSPACE (
echo ERROR: Environment variable WORKSPACE is not defined.
exit /B 1
)
if not exist "%WORKSPACE%\ProjectSettings\ProjectVersion.txt" (
echo ERROR: File "%WORKSPACE%\ProjectSettings\ProjectVersion.txt" does not exist.
exit /B 1
)
set "UNITY_FOLDER="
set "UNITY_VERSION="
for /F "usebackq tokens=2-4 delims=. " %%I in ("%WORKSPACE%\ProjectSettings\ProjectVersion.txt") do (
if not "%%~K" == "" (
for /F "delims=abcdef" %%L in ("%%~K") do (
set "UNITY_VERSION=%%~I.%%~J.%%~L"
for /D %%M in ("E:\Unity\%%~I.%%~J*") do set "UNITY_FOLDER=%%M"
)
)
)
if not defined UNITY_VERSION (
echo ERROR: Failed to determine unity version from "%WORKSPACE%\ProjectSettings\ProjectVersion.txt".
exit /B 1
)
if not defined UNITY_FOLDER (
echo ERROR: Failed to find a folder in "E:\Unity" for unity version %UNITY_VERSION%.
exit /B 1
)
echo Found for unity version %UNITY_VERSION% the folder "%UNITY_FOLDER%".
cd /D "%WORKSPACE%" 2>nul
if errorlevel 1 (
echo ERROR: Failed to set "%WORKSPACE%" as current folder.
exit /B
)
rem Other commands to execute.
endlocal
该批处理文件首先使用命令设置该批处理文件所需的执行环境SETLOCAL.
环境变量的存在WORKSPACE
接下来由批处理文件进行验证。该环境变量应由 Jenkins 在该批处理文件之外定义。如果缺少此重要环境变量的定义,则会输出错误消息。
然后检查文本文件是否存在,如果不存在则打印一条错误消息,并以退出代码 1 退出批处理文件。
两个环境变量UNITY_FOLDER
and UNITY_VERSION
如果在批处理文件之外偶然定义,则被删除。
接下来处理文本文件,该文件应仅包含一个包含感兴趣数据的非空行。否则,有必要更改代码以计算第一个子字符串(如果相等)m_EditorVersion:
在执行其他命令之前。
FOR有选项/F
解释包含在中的集合"
默认情况下作为字符串进行处理。但在这种情况下,字符串"
应解释为文件的完整限定文件名,其内容应逐行处理FOR。因此该选项usebackq
用于获取所需的文件内容处理行为。
FOR在处理文件内容时总是忽略空行。因此,文本文件顶部是否包含一个或多个空行并不重要。
FOR默认情况下,使用普通空格和水平制表符作为字符串分隔符将一行拆分为子字符串。如果第一个空格/制表符分隔字符串以分号开头,分号是删除所有前导空格/制表符后的默认行结束符,则该行也将被忽略FOR就像一个空行。最后,只有第一个空格/制表符分隔的字符串将被分配给指定的循环变量I
.
这里不需要这种默认的行处理行为,因为只是m_EditorVersion:
分配给指定的循环变量I
是不足够的。因此该选项delims=.
用于将线条按点和空格分开。选项tokens=2-4
通知FOR第二个空格/点分隔的子字符串2019
应该分配给循环变量I
,第三个空格/点分隔的子字符串3
到下一个循环变量J
这是中的下一个字符ASCII 表 https://www.asciitable.com/和第四个空格/点分隔的子字符串4f1
到下一个循环变量K
.
这里重要的是要指定delims=.
在选项参数字符串的末尾,以空格字符作为最后一个字符,因为空格字符否则会被解释为要忽略的选项分隔字符,就像之间的空格一样usebackq
and tokens=2-4
和之间的空间tokens=2-4
and delims=.
。事实上,也可以编写不带空格的选项,例如"usebackqtokens=2-4delims=. "
,但这使得带有选项的参数字符串难以阅读。
默认行尾定义eol=;
可以保留在这里,因为与 Unity 版本一致ProjectVersion.txt
在 0 个或多个空格/点之后没有分号,因此永远不会被忽略。
FOR在行中至少找到分配给循环变量的第二个空格/点分隔字符串后运行命令块中的命令I
,即非空字符串被分配给指定的循环变量I
。但只有当统一版本的所有三个部分都由以下方式确定时,才应执行命令FOR并分配给循环变量I
, J
and K
。因此,进行简单的字符串比较来验证循环变量值引用%%~K
不会扩展为空字符串,因为这意味着没有从文件中读取 Unity 版本的足够部分。
我不知道什么f1
编辑器版本末尾的意思是。那么再来一张FOR有选项/F
用于分割string 4f1
(no usebackq
在包含在的字符串上"
) 使用字符转换为子字符串abcdef
(小写十六进制字符)作为字符串分隔符并分配给指定的循环变量L
只是第一个子字符串。这应该永远不会失败,所以环境变量UNITY_VERSION
定义为2019.3.4
.
第三FOR在第二个内部执行FOR尽管由于没有引用循环变量,它也可能在外部L
。因此,这里也可以使用以下代码,得到相同的结果。
for /F "usebackq tokens=2-4 delims=. " %%I in ("%WORKSPACE%\ProjectSettings\ProjectVersion.txt") do (
if not "%%~K" == "" (
for /F "delims=abcdef" %%L in ("%%~K") do set "UNITY_VERSION=%%~I.%%~J.%%~L"
for /D %%M in ("E:\Unity\%%~I.%%~J*") do set "UNITY_FOLDER=%%M"
)
)
FOR有选项/D
和一组包含*
(or ?
) 导致在指定目录中搜索E:\Unity
对于名称开头的非隐藏目录2019.3
。每个非隐藏目录E:\Unity
匹配通配符模式2019.3*
首先用完整限定名称(驱动器+路径+名称)依次分配给循环变量M
和环境变量旁边UNITY_FOLDER
. FOR永远不会将文件/文件夹字符串包含在其中"
这就是为什么%%M
可以在这里使用%%~M
没有必要。分配给循环变量的文件夹名称M
从未包含在"
在这种情况下。所以环境变量UNITY_FOLDER
包含与文件系统返回的通配符模式匹配的最后一个文件夹以及完整路径。这意味着多个文件夹名称与通配符模式匹配2019.3*
文件系统决定最后分配给哪个文件夹名称UNITY_FOLDER
。 NTFS 在其主文件表中存储按本地特定字母顺序排序的目录项,而 FAT、FAT32 和 exFAT 在其文件分配表中存储未排序的目录项。
Note:如果实际上并不需要编辑器版本的第三个数字,因为它看起来像有问题的代码,也可以使用:
for /F "usebackq tokens=2-4 delims=. " %%I in ("%WORKSPACE%\ProjectSettings\ProjectVersion.txt") do (
if not "%%~J" == "" (
set "UNITY_VERSION=%%~I.%%~J"
for /D %%K in ("E:\Unity\%%~I.%%~J*") do set "UNITY_FOLDER=%%K"
)
)
如果代码可以成功确定 Unity 版本并找到匹配的 Unity 文件夹,则会进行两项附加检查。
The echo
批处理文件底部的命令行仅用于验证运行此批处理文件的结果WORKSPACE
在命令提示符窗口中的批处理文件之外定义,一切都按预期工作。
无需将工作区目录设为当前目录直至批处理文件末尾,但我添加了代码来验证将当前目录更改为工作区目录是否确实成功完成。
问题 1:文件/文件夹参数字符串未用引号引起来
运行时的帮助输出命令提示符 https://www.howtogeek.com/235101/10-ways-to-open-the-command-prompt-in-windows-10/ cmd /?
最后一页的最后一段解释了包含空格或这些字符之一的文件/文件夹参数字符串&()[]{}^=;!'+,`~
需要用直双引号引起来。因此,建议始终将不带路径或带路径的文件/文件夹名称括起来"
,尤其是由环境变量动态定义或从文件系统读取的一个或多个部分。
所以下面的命令行不太好:
cd %WORKSPACE%
IF NOT EXIST %WORKSPACE%\ProjectSettings\ProjectVersion.txt
SET /p TEST=<%WORKSPACE%\ProjectSettings\ProjectVersion.txt
更好的是使用:
cd "%WORKSPACE%"
IF NOT EXIST "%WORKSPACE%\ProjectSettings\ProjectVersion.txt"
SET /p TEST=<"%WORKSPACE%\ProjectSettings\ProjectVersion.txt"
可以在运行时读取简短的帮助输出cd /?
该命令CD不会将空格字符解释为参数分隔符,就像 Windows 命令处理器的大多数其他内部命令的情况一样cmd.exe
或目录中的可执行文件%SystemRoot%\System32
默认安装的,也属于Windows命令 https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/windows-commands据微软称。但是更改当前目录会因省略而失败"
如果目录路径偶然包含一个&符号,因为&
在双引号参数字符串之外已经被解释为cmd.exe
as AND执行之前的运算符CD正如我的回答中所描述的单行包含多个命令 https://stackoverflow.com/a/25344009/3074564.
最好利用周边"
在每个可能包含空格或的参数字符串上&()[]{}^=;!'+,`~
或重定向运算符<>|
Windows 命令处理器应将其解释为参数字符串的文字字符。嗯,方括号对于 Windows 命令处理器不再有特殊含义。[]
由于历史原因而在列表中COMMAND.COM
MS-DOS 的第一个版本并不总是将它们解释为文字字符。
问题 2:单个命令使用命令块
Windows 命令处理器主要用于
- 打开批处理文件,
- 从批处理文件中从先前记住的字节偏移量或第一行的偏移量 0 处读取一行,
- 解析和预处理这一行,
- 关闭批处理文件,不再需要读取任何行,
- 记住批处理文件中的当前字节偏移量,
- 执行命令行。
命令的帮助输出IF跑步时if /?
在第一页顶部显示一般语法,条件为真时执行的命令与命令位于同一行IF。命令的帮助输出FOR跑步时for /?
在第一页顶部显示一般语法,在每个循环迭代中执行的命令与命令位于同一行FOR。因此,这个推荐的语法应该用于IF条件和一个FOR只需执行一个命令的循环。
让我们看看 Windows 命令处理器如何解释以下内容IF环境变量条件WORKSPACE
被定义为C:\Temp
:
IF NOT EXIST %WORKSPACE%\ProjectSettings\ProjectVersion.txt (
EXIT 1
)
仅包含这三行的批处理文件会导致执行:
IF NOT EXIST C:\Temp\ProjectSettings\ProjectVersion.txt (EXIT 1 )
因此 Windows 命令处理器检测到有一个以(
,从批处理文件中读取更多行直至匹配)
,发现命令块仅由一个命令行组成,因此将三行合并为一个命令行。
因此,通过在批处理文件中写入,可以稍微加快批处理文件的处理速度:
IF NOT EXIST "%WORKSPACE%\ProjectSettings\ProjectVersion.txt" EXIT /B 1
然后需要更少的CPU指令来执行cmd.exe
.
IF NOT EXIST "C:\Temp\ProjectSettings\ProjectVersion.txt" EXIT /B 1
但是,使用命令块始终可以使批处理文件的代码具有更好的可读性。
如果可以避免对批处理文件进行大量文件打开、读取、关闭操作(有时会对批处理文件产生巨大影响),则将批处理文件的整个代码或经常执行的部分代码放入一个命令块中甚至可能很有用。总执行时间如下所示为什么 GOTO 循环比 FOR 循环慢得多并且还依赖于电源? https://stackoverflow.com/questions/53362979/
也可以看看Windows 命令解释器 (CMD.EXE) 如何解析脚本? https://stackoverflow.com/questions/4094699/
问题 3:ECHO。可能会导致不良行为
DosTips 论坛主题解释说ECHO.
可能无法输出文本或空行。的用法ECHO/
如果下一个字符不是更好?
最好的是ECHO(
.
分隔命令的字符ECHO如果保证后面有文本要输出,则从字符串到输出可以是标准参数分隔符空格ECHO
喜欢ECHO ProjectVersion.txt = %TEST%
.
ECHO/
最好输出一个空行。
ECHO(
最好是如果接下来有一个环境变量引用或循环变量引用,之前根本没有确定环境变量是否被定义,或者循环变量存在一个不以问号开头的非空字符串。
问题 4:使用 SET /P 从文本文件中读取一行
可以使用set /P
阅读first文本文件中的行并将该行分配给环境变量,如下所示:
SET /p TEST=<%WORKSPACE%\ProjectSettings\ProjectVersion.txt
但文本文件must将要分配给文件顶部的环境变量的文本。文本文件顶部的空行不会向环境变量分配任何内容,这意味着如果环境变量TEST
已经定义了,它的值根本没有改变,如果环境变量TEST
之前没有定义,执行后仍然没有定义SET.
最好使用命令FOR有选项/F
处理文本文件的内容。
问题 5:使用不带选项 /B 的命令 EXIT
命令EXIT退出正在处理批处理文件的 Windows 命令进程。它始终有效,但仍应避免使用EXIT没有选项/B
在大多数批处理文件中。
一个批处理文件,其中EXIT没有/B
不带或带退出代码的执行方式为cmd.exe
结果是cmd.exe
总是自行终止,即使在cmd.exe
使用选项隐式或显式启动/K
在完成命令、命令行或批处理文件的执行后保持命令进程运行,并且独立于批处理文件调用层次结构。
一个批处理文件EXIT没有选项/B
因此很难debug https://stackoverflow.com/a/42448601/3074564因为即使在命令提示符窗口中运行批处理文件而不是双击它来查看错误消息,命令进程和控制台窗口也会关闭cmd.exe
到达命令行EXIT.
问题 6:批处理文件依赖于外部定义的环境
设计良好的批处理文件不依赖于批处理文件外部定义的执行环境。这两个批处理文件使用的命令具有仅在启用命令扩展时才可用的功能。默认情况下启用命令扩展,默认情况下禁用延迟环境变量扩展,但如果批处理文件自己定义执行环境并在退出之前恢复以前的执行环境,那就更好了。这可以确保批处理文件始终按设计工作,即使调用该批处理文件的另一个批处理文件设置了不同的执行环境也是如此。
所以之后@echo off
以确保ECHO模式关闭后下一个命令行应该是:
setlocal EnableExtensions DisableDelayedExpansion
那么批处理文件肯定是在预期的环境中执行的。命令endlocal
应位于批处理文件的末尾以恢复初始执行环境。但 Windows 命令处理器隐式运行endlocal
在退出每个执行的批处理文件的处理之前setlocal
不执行匹配endlocal
在退出批处理文件处理之前。
的处决setlocal /?
and endlocal /?
结果显示这两个命令的帮助。更好的解释可以在这个答案 https://stackoverflow.com/a/67039507/3074564有关命令的更多详细信息SETLOCAL and ENDLOCAL.
的用法setlocal
在批处理文件的顶部设置所需的执行环境和endlocal
在批处理文件底部恢复初始执行环境必须明智地完成,以防批处理文件应通过环境变量将结果返回到初始执行环境,例如调用当前执行的批处理文件的父批处理文件。
问题 7:字母的用法ADFNPSTXZadfnpstxz
作为循环变量
命令的帮助FOR运行时输出for /?
描述可用于引用循环变量值的修饰符。
%~I - expands %I removing any surrounding quotes (")
%~fI - expands %I to a fully qualified path name
%~dI - expands %I to a drive letter only
%~pI - expands %I to a path only
%~nI - expands %I to a file name only
%~xI - expands %I to a file extension only
%~sI - expanded path contains short names only
%~aI - expands %I to file attributes of file
%~tI - expands %I to date/time of file
%~zI - expands %I to size of file
%~$PATH:I - searches the directories listed in the PATH
environment variable and expands %I to the
fully qualified name of the first one found.
If the environment variable name is not
defined or the file is not found by the
search, then this modifier expands to the
empty string
可以组合修饰符以获得复合结果:
%~dpI - expands %I to a drive letter and path only
%~nxI - expands %I to a file name and extension only
%~fsI - expands %I to a full path name with short names only
%~dp$PATH:I - searches the directories listed in the PATH
environment variable for %I and expands to the
drive letter and path of the first one found.
%~ftzaI - expands %I to a DIR like output line
修饰符被解释为不区分大小写,这意味着%~FI
是相同的%~fI
而循环变量的解释始终区分大小写,这意味着循环变量I
与循环变量的解释不同i
.
建议避免使用字母ADFNPSTXZadfnpstxz
作为循环变量,尽管这些字母也可以用作循环变量,特别是当循环变量引用与字符串连接时,如下面的批处理文件命令行示例所示。
for %%x in ("1" 2,3;4) do echo %%~xx5 = ?
直接在命令提示符窗口中执行的相同示例:
for %x in ("1" 2,3;4) do @echo %~xx5 = ?
输出一般(并非总是):
5 = ?
5 = ?
5 = ?
5 = ?
但使用输出更有意义I
在批处理文件中:
for %%I in ("1" 2,3;4) do echo %%~Ix5 = ?
直接在命令提示符窗口中执行的相同命令行:
for %I in ("1" 2,3;4) do @echo %~Ix5 = ?
在这种情况下,输出始终是:
1x5 = ?
2x5 = ?
3x5 = ?
4x5 = ?
所以无法使用ADFNPSTXZadfnpstxz
作为循环变量 if
- 循环变量值是用修饰符引用的,这意味着循环变量值引用以
%~
(命令提示符窗口)或%%~
(批处理文件)和
- 循环变量值引用与一个字符串连接,该字符串的第一个字符与循环变量所用的字母相同。
所以在命令提示符窗口中工作正常的是:
for %x in (1 2,3;4) do @echo %xx5 = ? & rem Condition 1 is not true.
for %n in ("1" 2,3;4) do @echo %~nx5 = ? & rem Condition 2 is not true.
for %x in ("1" 2,3;4) do @echo %~x+5 = ? & rem Condition 2 is not true.
但是,使用可用于引用带修饰符分配给循环变量的字符串值的字母时,可读性不佳。
在命令提示符窗口中使用的可读性示例:
for %i in (*) do @echo %~si
for %f in (*) do @echo %~sf
for %i in (*) do @echo %~sni
for %f in (*) do @echo %~snf
在这种情况下i
and f
两者都工作并且输出是相同的,独立于使用i
or f
。但更容易看出修饰符是什么(s
and n
)以及使用时的循环变量是什么i
并不是f
为循环变量。
也可以使用除字母之外的其他 ASCII 字符,对于 Windows 命令处理器没有特殊含义,例如#
如果不使用则作为循环变量FOR有选项/F
将多个子字符串分配给多个循环变量。
问题 8:FOR 处理不带通配符的集合
让我们看看使用以下代码到底会发生什么:
setlocal EnableExtensions EnableDelayedExpansion
set "TEST=m_EditorVersion: 2019.3.4f1"
for %%x in (%TEST::= %) do (
SET "VALUE=%%x"
SET "UNITY_VERSION=!VALUE:~0,-2!"
)
endlocal
字符串替换%TEST::= %
结果将每个冒号替换为分配给环境变量的字符串中的空格TEST
在解析FOR命令行及其命令块。所以字符串
m_EditorVersion: 2019.3.4f1
becomes
m_EditorVersion 2019.3.4f1
接下来 Windows 命令处理器替换了之间的两个空格m_EditorVersion
and 2019.3.4f1
通过一个空格作为清理。所以要处理的集合for
最终在解析和预处理命令行之后for
及其命令块:
m_EditorVersion 2019.3.4f1
该集合既不包含*
nor ?
。由于这个原因,命令FOR将集合解释为两个简单的空格分隔字符串以分配给指定的循环变量x
一个接着一个,并对这两个字符串执行命令块中的命令两次。
第一次迭代时m_EditorVersion
分配给环境变量VALUE
and m_EditorVersi
到环境变量UNITY_VERSION
。这并不是真正想要的,但是FOR再次运行这两个命令,这次是2019.3.4f1
分配给循环变量x
。所以在第二次循环迭代中2019.3.4f1
分配给环境变量VALUE
and 2019.3.4
到环境变量UNITY_VERSION
.
UNITY_VERSION
最终用所需的字符串定义,但是可以做得更好,如本答案顶部所示和解释。
我不太清楚为什么for
命令行结果出现错误消息:
“)”在这里无法进行语法处理。
这种事不应该发生FOR循环播放m_EditorVersion: 2019.3.4f1
被分配给环境变量TEST
.
Either TEST
定义了一个字符串,导致执行第二个批处理文件时出现语法错误,尽管根据说明不应出现这种情况,或者存在问题(
解释为命令块的开头,并且 Windows 命令处理器无法找到匹配的)
这标志着命令块的结束。