Есть несколько небольших проблем с кодом, которые я объясняю один за другим ниже, мое предложение для пакетного файла.
Задача получить 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
и UNITY_VERSION
удаляются, если они определены случайно вне пакетного файла.
Затем обрабатывается текстовый файл, который должен содержать только одну непустую строку с интересующими данными , В противном случае было бы необходимо изменить код для оценки первой подстроки, если она равна m_EditorVersion:
перед выполнением других команд.
FOR с параметром /F
интерпретирует вложенный набор в "
по умолчанию в качестве строки для обработки. Но в этом случае строка в "
должна интерпретироваться как полное имя файла, содержимое которого должно обрабатываться построчно FOR . По этой причине опция usebackq
используется для получения требуемого поведения при обработке содержимого файла.
FOR игнорирует всегда пустые строки при обработке содержимого файла. Поэтому не имеет значения, содержит ли текстовый файл вверху одну или несколько пустых строк.
FOR по умолчанию разбивает строку на подстроки, используя обычный пробел и символ горизонтальной табуляции в качестве разделителей строк. Если первая строка с пробелом / табуляцией начинается с точки с запятой, являющейся символом конца строки по умолчанию после удаления всех начальных пробелов / табуляций, строка также будет игнорироваться FOR , как пустая строка. Наконец, только первая строка с пробелом / табуляцией будет присвоена указанной переменной l oop I
.
Это поведение по умолчанию при обработке строки здесь не требуется, так как назначается только m_EditorVersion:
, назначенный указанному l oop переменная I
недостаточна. По этой причине опция delims=.
используется для разделения линии на точки и пробелы. Опция tokens=2-4
сообщает FOR , что второй подстроке с пробелами / точками с разделителями 2019
следует присвоить l oop переменную I
, третьей подстроке с пробелами / точками с разделителями 3
следующую л oop переменная J
, которая является следующим символом в таблице ASCII и четвертой подстрокой с пробелами / точками с разделителями 4f1
до следующей, кроме одной переменной l oop K
.
Здесь важно указать delims=.
в конце строки аргумента параметров с символом пробела в качестве последнего символа, поскольку символ пробела в противном случае интерпретируется как символ разделения параметров, который игнорируется, как пробел между usebackq
и tokens=2-4
и расстояние между tokens=2-4
и delims=.
. На самом деле можно также написать параметры без пробелов, например "usebackqtokens=2-4delims=. "
, но это затрудняет чтение строки аргумента с параметрами.
Здесь можно сохранить определение конца строки по умолчанию eol=;
поскольку строка с версией единицы в ProjectVersion.txt
не имеет точки с запятой после 0 или более пробелов / точек и по этой причине никогда не игнорируется.
FOR запускает команды в блок команд, когда в строке была найдена, по крайней мере, вторая строка, разделенная пробелом / точкой, назначенная переменной l oop I
, т. е. непустой строке назначена указанная переменная l oop I
. Но команды должны выполняться только в том случае, если все три части единой версии определены с помощью FOR и назначены переменным l oop I
, J
и K
. Поэтому выполняется простое сравнение строк, чтобы убедиться, что переменная l oop %%~K
не раскрывается в пустую строку, так как это означало бы, что из файла было прочитано недостаточно частей версии Unity.
Я не знаю не знаю, что означает f1
в конце версии редактора. Таким образом, еще один FOR с параметром /F
используется для разбиения строки 4f1
(без usebackq
на строку, заключенную в "
) на подстроки, используя символы abcdef
(шестнадцатеричные символы в нижнем регистре) в качестве разделителей строк и присваиваются указанной переменной l oop L
только первая подстрока. Это никогда не должно заканчиваться ошибкой, поэтому переменная окружения UNITY_VERSION
определяется с помощью 2019.3.4
.
Третий FOR выполняется внутри второго FOR , хотя это также может быть снаружи из-за отсутствия ссылки на l oop переменную 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
и набором, содержащим *
(или ?
), получается поиск в указанном каталоге E:\Unity
нескрываемого каталога, имя которого начинается с 2019.3
. Каждому не скрытому каталогу в E:\Unity
, соответствующему шаблону подстановочных знаков 2019.3*
, присваивается один за другим полное имя (диск + путь + имя), сначала l oop переменная M
и рядом с переменной окружения UNITY_FOLDER
. FOR никогда не заключает в себе строку файла / папки в "
, поэтому здесь можно использовать %%M
, а %%~M
не требуется. В этом случае имя папки, присвоенное переменной l oop M
, никогда не включается в "
. Таким образом, переменная окружения UNITY_FOLDER
содержит последнюю папку, соответствующую шаблону подстановочного знака, возвращенному файловой системой с полным путем. Это означает, что для нескольких имен папок, соответствующих шаблону с подстановочными знаками 2019.3*
, файловая система определяет, какое имя папки назначено последним для UNITY_FOLDER
. NTFS хранит записи каталога в своей главной таблице файлов, отсортированной в локальном порядке c alphabeti c, тогда как записи каталога FAT, FAT32 и exFAT не сортируются в своих таблицах размещения файлов.
Примечание: Если третий номер версии редактора на самом деле не нужен, как это выглядит в соответствии с рассматриваемым кодом, можно также использовать:
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.
Командная строка echo
в нижней части пакетного файла предназначена только для проверки результата при запуске этого пакетного файла с WORKSPACE
, определенным вне пакетного файла. в окне командной строки, и все работает как ожидалось.
Нет необходимости делать каталог рабочей области текущим каталогом до конца пакетного файла, но я добавил код, чтобы сделать это с проверкой при изменении текущий каталог к каталогу рабочего пространства был выполнен действительно успешно .
Проблема 1: Строки аргументов файла / папки не заключены в кавычки
Вывод справки при запуске в командной строке 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 Commands по данным Microsoft. Но изменить текущий каталог не удастся, если пропустить "
, если путь к каталогу случайно содержит амперсанд из-за &
вне строки аргумента, заключенного в двойные кавычки, уже интерпретируется cmd.exe
как оператор AND перед выполнением CD как описано, например, в моем ответе на одну строку с несколькими командами .
Лучше всего использовать окружение "
в каждой строке аргумента, которая может содержать пробел или &()[]{}^=;!'+,`~
или операторы перенаправления <>|
, которые должны интерпретироваться командным процессором Windows как буквенные символы строки аргумента. Что ж, квадратные скобки больше не имеют специального значения для командного процессора Windows. []
находятся в списке по историческим причинам, поскольку COMMAND.COM
первых версий MS-DOS интерпретировало их не всегда как буквенные символы.
Проблема 2: Использование блока команд для одного команда
Командный процессор Windows предназначен главным образом для
- открытия пакетного файла,
- чтения строки из пакетного файла из ранее запомненного байтового смещения или смещение 0 в первой строке,
- синтаксический анализ и предварительная обработка этой строки,
- закрытие пакетного файла, когда больше нет строк для чтения,
- запоминание текущего байтового смещения в пакетном файле,
- выполнение командной строки.
Вывод справки для команды IF при запуске if /?
показывает в верхней части первой страницы общий синтаксис, для которого команда Выполнить при условии, что истина находится в той же строке, что и команда IF . Вывод справки для команды FOR при запуске for /?
показывает в верхней части первой страницы общий синтаксис, по которому команда для выполнения на каждой итерации l oop находится в той же строке, что и команда ДЛЯ . Поэтому этот рекомендуемый синтаксис должен использоваться для условий IF и FOR l oop, для которых требуется выполнить только одну команду.
Давайте посмотрим, как Командный процессор 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
Тогда требуется меньше инструкций ЦП для выполнения cmd.exe
.
IF NOT EXIST "C:\Temp\ProjectSettings\ProjectVersion.txt" EXIT /B 1
Однако использование командного блока всегда позволяет сделать код o fa пакетный файл лучше читается.
Может быть даже полезно поместить весь код пакетного файла или часто выполняемую его часть в один командный блок, если это возможно, чтобы избежать большого количества открытий, чтения, закрытия файлов операции над командным файлом, который иногда оказывает драматическое влияние c на общее время выполнения, как показывает Почему GOTO l oop намного медленнее, чем FOR l oop и дополнительно зависит от источника питания?
См. Также Как Windows интерпретатор команд (CMD.EXE) анализирует сценарии?
Проблема 3: ECHO. может привести к нежелательному поведению
Форум DosTips topi c ECHO. СБОЙ дать текст или пустую строку - вместо этого используйте ECHO / , объясняющий, что ECHO.
может не вывести текст или пустую строку. Использование ECHO/
лучше, если следующий символ не ?
, а лучший ECHO(
.
Символ, отделяющий команду ECHO от строки для вывода, может быть стандартный разделитель аргументов, если гарантировано, что после ECHO
есть текст для вывода, например ECHO ProjectVersion.txt = %TEST%
.
ECHO/
- это хорошо для вывода пустой строки.
ECHO(
лучше всего, если рядом есть ссылка на переменную окружения или ссылка на переменную al oop, в которой не было уверенности, что переменная окружения вообще определена или переменная l oop существует с ненулевой переменной. пустая строка не начинается со знака вопроса.
Проблема 4: Использование SET / P для чтения строки из текстового файла
Для чтения можно использовать set /P
первая строка из текстового файла и назначение этой строки переменной среды, как это сделано с помощью:
SET /p TEST=<%WORKSPACE%\ProjectSettings\ProjectVersion.txt
Но текстовый файл должен иметь текст для назначения в переменную окружения в верхней части файла. Пустая строка в верхней части текстового файла приводит к тому, что переменной окружения ничего не назначается, что означает, что если переменная окружения TEST
уже определена, ее значение вообще не изменяется, а если переменная окружения TEST
не определена до этого он все еще не определен после выполнения SET .
Лучше использовать команду FOR с параметром /F
для обработки содержимого текста файл.
Проблема 5: Использование команды EXIT без опции / B
Команда EXIT выходит из командного процесса Windows, который обрабатывает пакетный файл , Это всегда работает, но, тем не менее, следует избегать использования EXIT без опции /B
в большинстве пакетных файлов.
Пакетный файл, в котором EXIT без /B
без или с кодом завершения выполняется cmd.exe
, в результате cmd.exe
всегда завершается сам по себе, даже если cmd.exe
запускается неявно или явно с опцией /K
, чтобы сохранить выполнение командного процесса после завершения выполнения команды, командная строка или пакетный файл и не зависит от иерархии вызовов пакетного файла.
Пакетный файл с EXIT без опции /B
поэтому трудно отладить из-за даже при запуске командного файла из окна командной строки вместо двойного щелчка по нему, чтобы увидеть сообщения об ошибках, командный процесс и окно консоли закрываются на cmd.exe
достигает командной строки с EXIT .
Проблема 6: Пакетный файл зависит от среды, определенной за пределами
Хороший пакетный файл не зависит от n среда выполнения, определенная вне пакетного файла. Два пакетных файла используют команды с функциями, доступными только с включенными расширениями команд. Расширения команд включены по умолчанию, а задержанное расширение переменных среды по умолчанию отключено, но, тем не менее, лучше, когда пакетный файл определяет саму среду выполнения и восстанавливает предыдущую среду выполнения перед выходом. Это гарантирует, что пакетный файл всегда работает как задумано, даже если другой пакетный файл, вызывающий этот пакетный файл, устанавливает другую среду выполнения.
То есть после @echo off
, чтобы убедиться, что ECHO Режим выключен, следующая командная строка должна быть:
setlocal EnableExtensions DisableDelayedExpansion
Тогда пакетный файл определенно выполняется в ожидаемой среде. Команда endlocal
должна находиться в конце командного файла для восстановления начальной среды выполнения. Но командный процессор Windows неявно запускает endlocal
перед выходом из обработки пакетного файла для каждого выполненного setlocal
без выполнения сопоставления endlocal
перед выходом из обработки пакетного файла.
Выполнение setlocal /?
и endlocal /?
приводит к отображению справки этих двух команд. Лучшее объяснение можно найти во второй половине этого ответа с гораздо более подробной информацией о командах SETLOCAL и ENDLOCAL .
Использование setlocal
в верхней части пакетного файла для настройки требуемой среды выполнения и endlocal
в нижней части пакетного файла для восстановления начальной среды выполнения должно быть просто выполнено с умом, если пакетный файл должен вернуться результаты через переменные среды в исходную среду выполнения, например в родительский пакетный файл, который вызвал исполняемый в данный момент пакетный файл.
Проблема 7: Использование букв ADFNPSTXZadfnpstxz
в качестве переменной l oop
Справка команды FOR , выводимая при запуске for /?
, описывает модификаторы, которые можно использовать при обращении к значению переменной al oop.
%~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
, тогда как переменная l oop интерпретируется всегда case -чувствительный, что означает, что l oop переменная I
интерпретируется иначе, чем l oop переменная i
.
Рекомендуется избегать буквы ADFNPSTXZadfnpstxz
как переменная l oop, хотя эти буквы также можно использовать как переменную l oop, особенно если ссылка на переменную al oop объединяется со строкой, как в примере ниже.
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 = ?
Вывод в этом случае всегда:
1x5 = ?
2x5 = ?
3x5 = ?
4x5 = ?
Можно также использовать другие символы ASCII, кроме букв без специального значения для Windows командного процессора, такого как #
или $
в качестве переменной l oop, если не используется FOR с опцией /F
, в которой несколько подстрок назначаются нескольким переменным l oop.
Проблема 8: Обработка набора без подстановочных знаков с помощью FOR
Let посмотрим, что в действительности происходит при использовании следующего кода:
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 co Строка mmand со своим блоком команд. Таким образом, строка
m_EditorVersion: 2019.3.4f1
становится
m_EditorVersion 2019.3.4f1
Далее Windows командный процессор заменяет два пробела между m_EditorVersion
и 2019.3.4f1
одним пробелом в качестве очистки. Таким образом, набор для обработки с помощью for
, наконец, выполняется после синтаксического анализа и предварительной обработки командной строки с помощью for
и ее блока команд:
m_EditorVersion 2019.3.4f1
Этот набор не содержит ни *
, ни ?
. По этой причине команда FOR интерпретирует набор как две простые строки, разделенные пробелом, для присвоения указанной переменной l oop x
одну за другой и выполнения команд в блоке команд для этих двух строк два раза. .
На первой итерации m_EditorVersion
присваивается переменной окружения VALUE
и m_EditorVersi
переменной окружения UNITY_VERSION
. Это на самом деле не требуется, но FOR запускает две команды еще раз, на этот раз с 2019.3.4f1
, назначенным переменной l oop x
. Таким образом, на втором этапе l oop итерация 2019.3.4f1
назначается переменной окружения VALUE
, а 2019.3.4
- переменной окружения UNITY_VERSION
.
UNITY_VERSION
определяется окончательно с требуемой строкой, но это можно сделать лучше, как показано и объяснено в верхней части этого ответа.
Мне не совсем понятно, почему в командной строке for
появляется сообщение об ошибке:
")" здесь не может быть синтаксически обработано.
Этого не должно происходить, если FOR l oop на m_EditorVersion: 2019.3.4f1
присвоено переменной окружения TEST
.
Либо TEST
определяется строкой, что приводит к синтаксической ошибке при выполнении второго пакетного файла, хотя это не должно иметь место в соответствии с описанием, либо существует проблема с (
, интерпретируемым как начало командный блок и командный процессор Windows не могут найти соответствующий )
, который отмечает конец командного блока.