Тема: CMD/BAT: возврат значений из процедур через параметры
Как известно, командный процессор NT-систем позволяет организовать подобие процедур на основе меток внутри пакетного файла и вызова нового контекста пакетного файла (А можно создать в bat-файле функцию?).
Использование процедур:
@echo off
setlocal enableextensions enabledelayedexpansion
set /a i=1
set /a j=2
call :Sub %i% %j%
echo i=%i%, j=%j%, k=%k%
endlocal
exit /b 0
rem ==========================================================================
rem ==========================================================================
:Sub
set /a k=%1 + %2
exit /b 0
rem ==========================================================================
i=1, j=2, k=3
Вызов процедуры производится с помощью команды «call», возврат из нового контекста в старый — командой «exit /b».
Но тут есть одна проблема: в новом контексте доступны все переменные среды старого контекста, что создаёт определённые проблемы. Можно, конечно, определить в процедуре собственный уровень локализации переменных среды командой «setlocal», однако при этом становятся недоступны все переменные среды «родительского» контекста, поскольку любая переменная будет являться локальной и будет существовать только внутри процедуры.
Переменная среды «k» является локальной для процедуры:
@echo off
setlocal enableextensions enabledelayedexpansion
set /a i=1
set /a j=2
call :Sub %i% %j%
echo i=%i%, j=%j%, k=%k%
endlocal
exit /b 0
rem ==========================================================================
rem ==========================================================================
:Sub
setlocal enableextensions enabledelayedexpansion
set /a k=%1 + %2
endlocal
exit /b 0
rem ==========================================================================
i=1, j=2, k=
Тем не менее, существует способ, позволяющий безболезненно использовать локальные переменные среды в процедуре и вместе с тем передавать значения в процедуру и возвращать значения в основную часть пакетного файла из процедуры.
@echo off
setlocal enableextensions enabledelayedexpansion
set /a i=1
set /a j=2
call :Sub %i% %j% k
echo i=%i%, j=%j%, k=%k%
endlocal
exit /b 0
rem ==========================================================================
rem ==========================================================================
:Sub
setlocal enableextensions enabledelayedexpansion
set /a Summa=%1 + %2
endlocal & set /a %3=%Summa%
exit /b 0
rem ==========================================================================
i=1, j=2, k=3
Передача входных параметров в процедуру осуществляется стандартным образом с помощью команды «call :<Метка процедуры>»:
call :Sub %i% %j% …
Выходные параметры указываются просто именем переменной:
call :Sub … k
Возврат значений производится в одной строке с командой «endlocal» процедуры как «set <номер параметра>=<значение>»:
endlocal & set /a %3=%Summa%
Только так и не иначе. Что при этом происходит? А происходит следующее:
1. Строка разбирается командным процессором и происходит раскрытие переменных среды. Таким образом, вышеуказанная строка превращается в:
endlocal & set /a k=3
поскольку параметр «%3» раскрывается в строку «k», а переменная среды «%Summa%», находящаяся внутри процедуры «:Sub», раскрывается в её значение «3».
2. Исполняется первая команда в очереди команд:
endlocal
При этом происходит закрытие локального контекста и становится доступным родительский контекст.
3. В родительском контексте исполняется вторая команда:
set /a k=3
в результате чего переменная «k» получает значение «3».
4. Командой
exit /b …
осуществляется возврат из процедуры.
Замечание:
Чтобы стало понятнее, приведу несколько неправильных способов.
set /a k=%Summa%
endlocal
В данном случае происходит присвоение значения локальной переменной «k», что никак не отразится на родительском контексте.
set /a %3=%Summa%
endlocal
То же самое. Параметр %3 раскрывается в значение «k», а результат исполнения команды «set /a k=…» мы рассмотрели чуть выше.
endlocal
set /a %3=%Summa%
Здесь переменная %Summa% из контекста процедуры уже не существует (в понимании командных файлов будет равна пустому значению), в результате чего данная команда выродится в «set /a k=». Но мы ведь не этого хотели, правда?
set /a %3=%Summa% & endlocal
«А почему бы команды не поменять местами? Какая нам разница?» А вот какая. Вначале будет выполняться команда «set /a %3=%Summa%». После раскрытия переменных мы получим вроде бы ту же самую правильную команду «set /a k=3». Однако, в этот момент мы ещё находимся внутри локального контекста процедуры, и переменная «k» — тоже будет локальная. Значит, следующей командой («endlocal») эта переменная будет уничтожена. Вот почему такой способ тоже не годится.
Остаётся только добавить, что на данный метод я наткнулся, разбираясь со скриптом TSCMD032 FAQ: How can I convert a decimal number to binary, octal, hexadecimal? Assorted NT/2000/XP/.. CMD.EXE script tricks written by Timo Salmi, в момент публикации обнаружилось, что на том же сайте существует и отдельная статья на эту тему: TSCMD018 FAQ: How does one build re-usable script function modules? Assorted NT/2000/XP/.. CMD.EXE script tricks written by Timo Salmi.