我正在尝试为自动Unity构建设置Jenkins服务器。
因此,我写了两个(在我看来)基本相同的批处理脚本。
这两个脚本都是由Jenkins通过Execute Windows batch command
步骤作为构建步骤运行的,
命令:E:\unityImport.bat
并且在此之后,第二Execute Windows batch command
步骤使用
命令:E:\unityBuild.bat
他们都有相同的开始,因为我需要收集一些文件路径,特别是项目的统一版本。因此,在这两个脚本中,我使用完全相同的方式解析和字符串拆分项目版本。它们之间唯一不同的是,第一个启动Unity并将专用的unitypackage
(其中包含下一步要执行的方法)导入到项目中,而第二个再次启动Unity以执行实际的构建(不幸的是,它并没有一次完成)。Unity似乎试图在导入unitypackage
之前执行该方法)。
但是,第二个脚本总是失败,并出现语法错误
“)”不能在此处按语法处理。
我想做的是
1.读取文件%WORKSPACE%\ProjectSettings\ProjectVersion.txt
的内容
SET /p TEST=<%WORKSPACE%\ProjectSettings\ProjectVersion.txt
%TEST%
的内容通常看起来像这样。
m_EditorVersion: 2019.3.4f1
ECHO. ProjectVersion.txt = %TEST%
看起来像
ProjectVersion.txt = m_EditorVersion: 2019.3.4f1
1.拆分字符串,以便只取包含版本号的最后一部分
for %%x in (%TEST::= %) do (
SET "VALUE=%%x"
SET "UNITY_VERSION=!VALUE:~0,-2!"
)
所以%UNITY_VERSION%
通常包含例如2019.3.4
。我不分开更多的,因为也有统一的版本与两位数,如。2018.4.18
1.在.
上拆分字符串,以便仅获取主版本号
for /f "tokens=1,2 delims=." %%a in ("%UNITY_VERSION%") do (
SET "A=%%a"
SET "B=%%b"
)
SET "UNITY_VERSION=%A%.%B%"
这导致%UNITY_VERSION%
例如是2019.3
1.最后搜索所有已安装的Unity版本(如果存在所需版本)
set "UNITY_FOLDER="
for /f "delims=" %%a in ('dir /b E:\Unity\%UNITY_VERSION%*') do (
set "UNITY_FOLDER=%%a"
)
在此之后,我们要么找到了给定版本的有效Unity安装文件夹,要么没有。
这是剧本。
导入(按预期工作)
@ECHO OFF
CLS
ECHO.
cd %WORKSPACE%
IF NOT EXIST %WORKSPACE%\ProjectSettings\ProjectVersion.txt (
EXIT 1
)
SETLOCAL ENABLEDELAYEDEXPANSION
SET /p TEST=<%WORKSPACE%\ProjectSettings\ProjectVersion.txt
ECHO. ProjectVersion.txt = %TEST%
for %%x in (%TEST::= %) do (
SET "VALUE=%%x"
SET "UNITY_VERSION=!VALUE:~0,-2!"
)
for /f "tokens=1,2 delims=." %%a in ("%UNITY_VERSION%") do (
SET "A=%%a"
SET "B=%%b"
)
SET "UNITY_VERSION=%A%.%B%"
ECHO. Project Unity Version = %UNITY_VERSION%
set "UNITY_FOLDER="
for /f "delims=" %%a in ('dir /b E:\Unity\%UNITY_VERSION%*') do (
set "UNITY_FOLDER=%%a"
)
IF "%UNITY_FOLDER%"=="" (
EXIT 1
)
ECHO. Using Unity Version %UNITY_FOLDER%
ECHO. Running:
ECHO. E:\Unity\%UNITY_FOLDER%\Editor\Unity.exe -quit -batchmode -projectPath %WORKSPACE% -logFile - -importPackage E:\UnityBuildPackage\AutoBuilder.unitypackage
E:\Unity\%UNITY_FOLDER%\Editor\Unity.exe -quit -batchmode -projectPath %WORKSPACE% -logFile - -importPackage E:\UnityBuildPackage\AutoBuilder.unitypackage
IF NOT %errorlevel% equ 0 (
EXIT 1
)
EXIT 0
Build(这会失败,因为语法错误,我会用REM HERE IT BREAKS! ...
标记,实际脚本中不存在)
@ECHO OFF
CLS
ECHO.
cd %WORKSPACE%
IF NOT EXIST %WORKSPACE%\ProjectSettings\ProjectVersion.txt (
EXIT 1
)
SETLOCAL ENABLEDELAYEDEXPANSION
SET /p TEST=<%WORKSPACE%\ProjectSettings\ProjectVersion.txt
ECHO. ProjectVersion.txt = %TEST%
REM HERE IT BREAKS! The before echo is the last I see before getting the syntax error
for %%x in (%TEST::= %) do (
SET "VALUE=%%x"
SET "UNITY_VERSION=!VALUE:~0,-2!"
)
for /f "tokens=1,2 delims=." %%a in ("%UNITY_VERSION%") do (
SET "A=%%a"
SET "B=%%b"
)
SET "UNITY_VERSION=%A%.%B%"
ECHO. Project Unity Version = %UNITY_VERSION%
set "UNITY_FOLDER="
for /f "delims=" %%a in ('dir /b E:\Unity\%UNITY_VERSION%*') do (
set "UNITY_FOLDER=%%a"
)
IF "%UNITY_FOLDER%"=="" (
EXIT 1
)
ECHO. Using Unity Version %UNITY_FOLDER%
...
我不认为其余的问题,因为正如所说,我在控制台看到它已经打破后,例如。
ProjectVersion.txt = 2019.3.4f1
“)”不能在此处按语法处理。
有没有人看到这个错误,或者Jenkins可能有什么东西导致第二个脚本失败,并出现语法错误,尽管据我所知,它们基本上是相同的?
1条答案
按热度按时间ogq8wdun1#
代码中有多个小问题,我会在我对批处理文件的建议下面逐一解释。
根据文件
ProjectVersion.txt
中定义的UNITY_VERSION
获取UNITY_FOLDER
的任务可以通过使用以下代码更有效地完成:此批处理文件首先使用命令SETTABLE设置此批处理文件所需的执行环境。
接下来通过批处理文件验证环境变量
WORKSPACE
的存在。这个环境变量应该由Jenkins在这个批处理文件之外定义。如果缺少此重要环境变量的定义,则会输出错误消息。然后检查文本文件是否存在,如果不存在则打印错误消息,并使用退出代码1退出批处理文件。
如果在批处理文件外部偶然定义了两个环境变量
UNITY_FOLDER
和UNITY_VERSION
,则会将其删除。接下来,处理文本文件,该文件应该只包含一个包含感兴趣数据的非空行。否则,在执行其他命令之前,如果第一个子字符串等于
m_EditorVersion:
,则需要更改代码以评估第一个子字符串。带有
/F
选项的FOR默认将"
中包含的集合解释为要处理的字符串。但在这种情况下,"
中的字符串应被解释为文件的完整限定文件名,其内容应逐行处理FOR。因此,选项usebackq
用于获取所需的文件内容处理行为。FOR在处理文件内容时忽略始终为空行。因此,如果文本文件在顶部包含一个或多个空行,则没有关系。
默认情况下,FOR使用普通空格和水平制表符作为字符串分隔符将一行拆分为子字符串。如果第一个空格/制表符分隔的字符串在删除所有前导空格/制表符后以一个默认的行尾字符开始,那么该行也会被FOR忽略,就像一个空行一样。最后,第一个空格/制表符分隔的字符串将被分配给指定的循环变量
I
。这里不需要这种默认的行处理行为,因为仅仅将
m_EditorVersion:
赋给指定的循环变量I
是不够的。出于这个原因,选项delims=.
用于将行拆分为点和空格。选项tokens=2-4
通知FOR第二个空格/点分隔的子字符串2019
应该分配给循环变量I
,第三个空格/点分隔的子字符串3
到下一个循环变量J
,它是ASCII table中的下一个字符,第四个空格/点分隔子串4f1
到下一个循环变量K
。这里重要的是在选项参数字符串的末尾指定
delims=.
,并将空格字符作为最后一个字符,因为空格字符会被解释为选项分隔字符,例如usebackq
和tokens=2-4
之间的空格以及tokens=2-4
和delims=.
之间的空格。事实上,也可以编写没有空格的选项,如"usebackqtokens=2-4delims=. "
,但这会使带有选项的参数字符串难以阅读。默认的行结束定义
eol=;
可以保留在这里,因为在ProjectVersion.txt
中具有unity版本的行在0或更多的空格/点之后没有一个空格,并且永远不会因为这个原因而被忽略。FOR运行命令块中的命令,在行中找到至少第二个分配给循环变量
I
的空格/点分隔字符串,即将非空字符串分配给指定的循环变量I
。但是,只有当统一版本的所有三个部分都由FOR确定并分配给循环变量I
,J
和K
时,才应该执行命令。因此,进行一个简单的字符串比较,以验证循环变量值引用%%~K
没有扩展为空字符串,因为这意味着没有从文件中读取足够的统一版本部分。我不知道编辑器版本末尾的
f1
是什么意思。因此,再使用一个带有/F
选项的FOR将string4f1
("
中包含的字符串上没有usebackq
)拆分为子字符串,使用字符abcdef
(小写十六进制字符)作为字符串分隔符,并将指定的循环变量L
仅分配给第一个子字符串。这应该永远不会失败,因此环境变量UNITY_VERSION
被定义为2019.3.4
。第三个FOR在第二个FOR内部执行,尽管它也可以在外部执行,因为没有引用循环变量
L
。所以下面的代码也可以用在这里,得到同样的结果。FOR带有选项
/D
和包含*
(或?
)的集合将导致在指定目录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则将目录项存储在其文件分配表中,但不进行排序。**注意:**如果第三个编辑器版本号不是真正需要的,因为它看起来像根据问题的代码,也可以使用:用途:
如果代码可以成功确定unity版本并找到匹配的unity文件夹,则进行两次额外检查。
批处理文件底部的
echo
命令行只是为了验证在命令提示符窗口中使用批处理文件外部定义的WORKSPACE
运行此批处理文件的结果,并且一切都按预期工作。不需要将工作区目录设置为批处理文件末尾的当前目录,但是我添加了代码来验证是否成功地将当前目录更改为工作区目录。
第1期:文件/文件夹参数字符串未用引号括起来
在command prompt
cmd /?
中运行的帮助输出在最后一页的最后一段解释了包含空格或其中一个字符&()[]{}^=;!'+,
~的文件/文件夹参数字符串需要用双引号括起来。因此,建议始终在
"`中包含不带路径或带路径的文件/文件夹名称,特别是由环境变量动态定义或从文件系统读取的一个或多个部分。下面的命令行不太好:
最好使用:
在运行
cd /?
的简短帮助输出中可以看到,命令CD不会将空格字符解释为参数分隔符,就像Windows命令处理器cmd.exe
的大多数其他内部命令或目录%SystemRoot%\System32
中的可执行文件一样,这些命令默认安装并且根据Microsoft也属于Windows commands。但是,如果目录路径偶然包含一个与号,则更改当前目录会失败,因为cmd.exe
在执行CD之前已经将&
外部的双引号参数字符串解释为AND运算符,例如我在single line with multiple commands上的回答中所描述的。最好在每个可能包含空格或
&()[]{}^=;!'+,
~或重定向操作符
<>|的参数字符串上使用包围
",这些操作符应被Windows命令处理器解释为参数字符串的文字字符。好吧,方括号对Windows命令处理器不再有特殊意义。
[]在列表中是由于历史原因,因为MS-DOS的第一个版本的
COMMAND.COM`并不总是将它们解释为文字字符。第2期:单个命令的命令块用法
Windows命令处理器主要用于
运行
if /?
时命令IF的帮助输出在第一页的顶部显示了在条件为true时执行的命令与命令IF位于同一行的常规语法。运行for /?
时命令FOR的帮助输出在第一页的顶部显示了在每次循环迭代中执行的命令与命令FOR位于同一行的常规语法。因此,此推荐语法应用于只需要执行一个命令的IF条件和FOR循环。让我们看看Windows命令处理器如何解释以下IF条件,其中环境变量
WORKSPACE
是用C:\Temp
定义的:只有这三行的批处理文件将导致执行:
因此,Windows命令处理器检测到有一个以
(
开头的命令块,从批处理文件中读取更多行,直到匹配)
,发现命令块仅由一个命令行组成,并因此将三行合并到一个命令行中。因此,批处理文件处理可以通过在批处理文件中写入来加快一点点:
然后,
cmd.exe
需要更少的CPU指令来执行。然而,使用命令块总是可以使批处理文件的代码更好地可读。
如果可以避免对批处理文件执行大量的文件打开、读取和关闭操作,甚至可以将经常执行的批处理文件的整个代码或部分代码放在一个命令块中,这些操作有时会对总执行时间产生巨大影响,如Why is a GOTO loop much slower than a FOR loop and depends additionally on power supply?所示。
参见How does the Windows Command Interpreter (CMD.EXE) parse scripts?
第三期:ECHO.可能导致不必要的行为
DosTips论坛主题ECHO. FAILS to give text or blank line - Instead use ECHO/解释了
ECHO.
可能无法输出文本或空行。如果下一个字符不是?
,则使用ECHO/
更好,最好是ECHO(
。如果保证在
ECHO
之后有文本要输出,则将命令ECHO与要输出的字符串分隔的字符可以是标准参数分隔符空格。ECHO/
可以输出一个空行。ECHO(
是最好的,如果有下一个环境变量引用或循环变量引用,在此之前没有确定环境变量是否定义,或者循环变量存在非空字符串,而不是以问号开头。第四期:使用SET /P从文本文件中读取一行
可以使用
set /P
从文本文件中读取第一行,并将该行分配给环境变量,如下所示:但是文本文件必须在文件顶部有要分配给环境变量的文本。文本文件顶部的一个空行会导致环境变量没有任何赋值,这意味着如果环境变量
TEST
已经定义,则其值根本不会改变,如果环境变量TEST
之前没有定义,则执行SET后仍然没有定义。最好使用带有
/F
选项的命令FOR来处理文本文件的内容。第五期:使用不带选项/B的EXIT命令
命令EXIT退出正在处理批处理文件的Windows命令进程。它总是有效的,但是在大多数批处理文件中应该避免使用不带
/B
选项的EXIT。如果
cmd.exe
在批处理文件上执行EXIT(不带/B
,不带退出代码或带退出代码),则cmd.exe
始终会自行终止,即使在cmd.exe
上隐式或显式启动,并使用选项/K
在完成命令执行后保持命令进程运行,命令行或批处理文件,并且独立于批处理文件调用层次。一个批处理文件EXIT没有选项
/B
因此很难debug,因为即使在运行批处理文件从命令提示符窗口,而不是双击它看到错误消息,命令进程和控制台窗口关闭cmd.exe
到达命令行与EXIT。第六期:批处理文件取决于外部定义的环境
设计良好的批处理文件不依赖于在批处理文件外部定义的执行环境。这两个批处理文件使用的命令的功能仅在启用命令扩展名时可用。默认情况下,命令扩展是启用的,延迟环境变量扩展是禁用的,但是当批处理文件将自己定义为执行环境并在退出之前恢复以前的执行环境时,情况会更好。这样可以确保批处理文件始终按设计工作,即使调用此批处理文件的另一个批处理文件设置了不同的执行环境。
因此,在
@echo off
之后,为了确保关闭ECHO模式,下一个命令行应该是:那么批处理文件肯定会在预期的环境中执行。命令
endlocal
应位于批处理文件的末尾,以恢复初始执行环境。但是Windows命令处理器在退出批处理文件处理之前隐式地运行endlocal
,对于每个执行的setlocal
,在退出批处理文件处理之前没有执行匹配的endlocal
。执行
setlocal /?
和endlocal /?
会显示这两个命令的帮助。在this answer中可以找到一个更好的解释,其中包含有关命令SETSTACK和ENDSTACK的更多细节。在批处理文件的顶部使用
setlocal
来设置所需的执行环境,在批处理文件的底部使用endlocal
来恢复初始执行环境,必须明智地进行,以防批处理文件通过环境变量将结果返回到初始执行环境,如调用当前执行的批处理文件的父批处理文件。第七期:使用字母
ADFNPSTXZadfnpstxz
作为循环变量运行
for /?
时命令FOR输出的帮助描述了可用于引用循环变量值的修饰符。可以组合这些修改器以获得复合结果:
修饰符被解释为不区分大小写,这意味着
%~FI
与%~fI
相同,而循环变量被解释为始终区分大小写,这意味着循环变量I
被解释为与循环变量i
不同。建议避免将字母
ADFNPSTXZadfnpstxz
作为循环变量,尽管这些字母也可以用作循环变量,特别是如果循环变量引用与下面的批处理文件命令行示例中的字符串连接。直接在命令提示符窗口中执行的相同示例:
输出通常是(不总是):
但是在批处理文件中使用
I
的输出更有意义:直接在命令提示符窗口中执行的相同命令行:
在这种情况下,输出总是:
因此,在以下情况下,不可能使用
ADFNPSTXZadfnpstxz
作为循环变量:1.循环变量值是用一个修饰符引用的,这意味着循环变量值引用以
%~
(命令提示符窗口)或%%~
(批处理文件)开头,1.循环变量值引用与第一个字符与用于循环变量的字母相同的字符串连接。
所以在命令提示符窗口中工作良好的是:
然而,使用一个字母来引用赋给带有修饰符的循环变量的字符串值,可读性不好。
在命令提示符窗口中使用的可读性示例:
在这种情况下,
i
和f
都可以工作,输出是相同的,与i
或f
的使用无关。但是,使用i
而不是f
作为循环变量时,更容易看到修改器(s
和n
)以及循环变量。也可以使用其他ASCII字符而不是对Windows命令处理器没有特殊意义的字母,如
#
作为循环变量,如果不使用带有选项/F
的FOR,其中多个子字符串被分配给多个循环变量。第八期:用FOR处理不带通配符的集合
让我们看看使用下面的代码实际上发生了什么:
字符串替换
%TEST::= %
会导致在使用命令块解析FOR命令行时,将分配给环境变量TEST
的字符串中的每个冒号替换为空格。所以绳子成为
接下来,Windows命令处理器将
m_EditorVersion
和2019.3.4f1
之间的两个空格替换为单个空格作为清理。因此,for
处理的set最终是在使用for
及其命令块解析和预处理命令行之后:这个集合既不包含
*
也不包含?
。出于这个原因,命令FOR将set解释为两个简单的空格分隔字符串,依次分配给指定的循环变量x
,并为这两个字符串执行命令块中的命令两次。在第一次迭代中,
m_EditorVersion
被分配给环境变量VALUE
,m_EditorVersi
被分配给环境变量UNITY_VERSION
。这并不是真正想要的,但是FOR再次运行这两个命令,这次将2019.3.4f1
赋值给循环变量x
。因此,在第二次循环迭代中,2019.3.4f1
被分配给环境变量VALUE
,2019.3.4
被分配给环境变量UNITY_VERSION
。UNITY_VERSION
最后是用想要的字符串定义的,但是可以做得更好,如这个答案顶部所示和解释的那样。我不太清楚为什么
for
命令行会导致错误消息:“)”不能在此处按语法处理。
对于
m_EditorVersion: 2019.3.4f1
上的这个FOR循环来说,这不应该发生,因为它被分配给了环境变量TEST
。要么
TEST
是用一个字符串定义的,导致在执行第二个批处理文件时出现语法错误,尽管根据描述不应该是这种情况,要么(
被解释为命令块的开始,Windows命令处理器无法找到标记命令块结束的匹配)
。